一般 Vue
项目中的数据通讯,咱们经过如下三种方式就能够解决,可是随着项目多层嵌套的组件增长,兄弟组件间的状态传递很是繁琐,致使不断的经过事件来变动状态,同步状态多份拷贝,最后代码难以维护。因而尤大大开发了 Vuex
来解决这个问题。javascript
props
;$emit
;eventBus
事件总线。固然中小 Vue
项目能够不使用 Vuex
,当出现下面这两种状况的时候咱们就应该考虑使用 Vuex
统一管理状态了。html
使用Vuex
的优势也很明显:vue
vue-devtools
来进行状态相关的bug排查。官方 Vuex
上有一张用于解释 Vuex
的图,可是并无给于清晰明确的注释。这里简单说下每块的功能和做用,以及整个流程图的单向数据量的流向。java
Vue Components
:Vue组件。HTML页面上,负责接收用户操做等交互行为,执行 dispatch
方法触发对应 action
进行回应。vuex
dispatch
:操做行为触发方法,是惟一能执行action的方法。缓存
actions
:操做行为处理模块。负责处理Vue Components接收到的全部交互行为。包含同步/异步操做,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操做就在这个模块中进行,包括触发其余 action
以及提交 mutation
的操做。该模块提供了Promise的封装,以支持action的链式触发。架构
commit
:状态改变提交操做方法。对 mutation
进行提交,是惟一能执行mutation的方法。app
mutations
:状态改变操做方法。是Vuex修改state的惟一推荐方法,其余修改方式在严格模式下将会报错。该方法只能进行同步操做,且方法名只能全局惟一。操做之中会有一些hook暴露出来,以进行state的监控等。异步
state
:页面状态管理容器对象。集中存储 Vue components
中 data
对象的零散数据,全局惟一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。函数
Vue组件
接收交互行为,调用 dispatch
方法触发 action
相关处理,若页面状态须要改变,则调用 commit
方法提交 mutation
修改 state
,经过 getters
获取到 state
新值,从新渲染 Vue Components
,界面随之更新。
总结:
state
里面就是存放的咱们上面所提到的状态。
mutations
就是存放如何更改状态。
getters
就是从 state
中派生出状态,好比将 state
中的某个状态进行过滤而后获取新的状态。
actions
就是 mutation
的增强版,它能够经过 commit
mutations中的方法来改变状态,最重要的是它能够进行异步操做。
modules
顾名思义,就是当用这个容器来装这些状态仍是显得混乱的时候,咱们就能够把容器分红几块,把状态和管理规则分类来装。这和咱们建立js模块是一个目的,让代码结构更清晰。
咱们作的项目中使用Vuex,在使用Vuex的过程当中留下了一些疑问,发如今使用层面并不能解答个人疑惑。因而将疑问简单罗列,最近在看了 Vuex
源码才明白。
state
的修改只能在 mutation
的回调函数中?mutations
里的方法,为何能够修改 state
?this.commit
来调用 mutation
函数?actions
函数中context对象
,为何不是 store实例
自己?actions函数
里能够调用 dispatch
或者 commit
?this.$store.getters.xx
,是如何能够访问到 getter
函数的执行结果的?针对以上疑问,在看Vuex源码的过程当中慢慢解惑了。
state
的修改只能在 mutation
的回调函数中?在Vuex
源码的 Store
类中有个 _withCommit
函数:
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
复制代码
Vuex
中全部对 state
的修改都会调用 _withCommit
函数的包装,保证在同步修改 state 的过程当中 this._committing
的值始终为 true
。当咱们检测到 state
变化的时候,若是 this._committing
不为 true
,则能查到这个状态修改有问题。
在Vuex
实例化的时候,会调用 Store
,Store
会调用 installModule
,来对传入的配置进行模块的注册和安装。对 mutations
进行注册和安装,调用了 registerMutation
方法:
/** * 注册mutation 做用同步修改当前模块的 state * @param {*} store Store实例 * @param {*} type mutation 的 key * @param {*} handler mutation 执行的函数 * @param {*} local 当前模块 */
function registerMutation (store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = [])
entry.push(function wrappedMutationHandler (payload) {
handler.call(store, local.state, payload)
})
}
复制代码
该方法对mutation方法进行再次封装,注意 handler.call(store, local.state, payload)
,这里改变 mutation
执行的函数的 this
指向为 Store实例
,local.state
为当前模块的 state
,payload
为额外参数。
由于改变了 mutation
执行的函数的 this
指向为 Store实例
,就方便对 this.state
进行修改。
this.commit
来调用 mutation
函数?在 Vuex 中,mutation 的调用是经过 store 实例的 API 接口 commit 来调用的。来看一下 commit 函数的定义:
/** * * @param {*} _type mutation 的类型 * @param {*} _payload 额外的参数 * @param {*} _options 一些配置 */
commit (_type, _payload, _options) {
// check object-style commit
// unifyObjectStyle 方法对 commit 多种形式传参 进行处理
// commit 的载荷形式和对象形式的底层处理
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
// 根据 type 去查找对应的 mutation
const entry = this._mutations[type]
// 没查到 报错提示
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
// 使用了 this._withCommit 的方法提交 mutation
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 遍历 this._subscribers,调用回调函数,并把 mutation 和当前的根 state 做为参数传入
this._subscribers.forEach(sub => sub(mutation, this.state))
if (
process.env.NODE_ENV !== 'production' &&
options && options.silent
) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
复制代码
this.commmit()
接收mutation的类型和外部参数,在 commmit
的实现中经过 this._mutations[type]
去匹配到对应的 mutation
函数,而后调用。
context对象
,为何不是store实例自己?actions函数
里能够调用 dispatch
或者 commit
?actions的使用:
actions: {
getTree(context) {
getDepTree().then(res => {
context.commit('updateTree', res.data)
})
}
}
复制代码
在action的初始化函数中有这样一段代码:
/** * 注册actions * @param {*} store 全局store * @param {*} type action 类型 * @param {*} handler action 函数 * @param {*} local 当前的module */
function registerAction (store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = [])
entry.push(function wrappedActionHandler (payload) {
let res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload)
if (!isPromise(res)) {
res = Promise.resolve(res)
}
// store._devtoolHook 是在store constructor的时候执行 赋值的
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
复制代码
很明显context对象是指定的,并非store实例, const {dispatch, commit, getters, state, rootGetters,rootState } = context
context对象上挂载了:
this.$store.getters.xx
,是如何能够访问到getter函数的执行结果的?在Vuex源码的Store实例的实现中有这样一个方法 resetStoreVM
:
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
Object.keys(wrappedGetters).forEach(key => {
const fn = wrappedGetters[key]
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key]
})
})
// ...
store._vm = new Vue({
data: { state },
computed
})
// ...
}
复制代码
遍历 store._wrappedGetters
对象,在遍历过程当中拿到每一个 getter
的包装函数,并把这个包装函数执行的结果用 computed
临时保存。
而后实例化了一个 Vue实例
,把上面的 computed
做为计算属性传入,把 状态树state
做为 data
传入,这样就完成了注册。
咱们就能够在组件中访问 this.$store.getters.xxgetter
了,至关于访问了 store._vm[xxgetter]
,也就是在访问 computed[xxgetter]
,这样就访问到 xxgetter
的回调函数了。