store 将应用的状态集中起来,但若是应用变得很是复杂时,即状态很是的多时,store 就有可能变得至关臃肿。module 可以帮 store 划分了模块,每一个模块都拥有本身的 state、getter、mutation、action 和 module。vue
那么 module 又是怎样进行划分的,划分后的模块又是如何管理本身的状态呢?接下来就来解读 module 的实现吧。vuex
解读前,须要对如下知识有所了解:api
在 vuex 文档里有这么一句话:默认状况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块可以对同一 mutation 或 action 做出响应。数组
什么意思呢?先看看如下示例:模块化
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
addNote () {
console.log('root addNote')
}
},
modules: {
a: {
state: {
count: 0
},
mutations: {
addNote () {
console.log('module a addNote')
}
}
}
}
})
复制代码
使用了 module 以后,state 则会被模块化。好比要调用根模块的 state,则调用 store.state.count
,若是要调用 a 模块的 state,则调用 store.state.a.count
。ui
可是示例中的 mutation 则是注册在全局下的,即调用 store.commit('addNote')
,将会调用跟模块和 a 模块的 mutation。除非区分各模块 mutation 的命名,不然,在同名的状况下,只要 commit 后就会被触发调用。spa
固然,vuex 2.0.0 后面的版本添加了命名空间
的功能,使得 module 更加的模块化。prototype
因此接下来要解读的 module 中,实际上只要 state 是被模块化了, action、mutation 和 getter 仍是在全局的模块下。code
installModule 里实现了 module 的注册,定位到 installModule 方法。递归
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const {
modules
} = module
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
Vue.set(parentState, moduleName, state || {})
})
}
// mutation 的注册
// action 的注册
// getter 的注册
if (modules) {
Object.keys(modules).forEach(key => {
installModule(store, rootState, path.concat(key), modules[key], hot)
})
}
}
复制代码
看到简化后的代码,能够看出 installModule 对 module 作了两步初始化操做。第一步是使用 Vue.set() 对当前的 module 的 state 设置了监听;第二步则是继续遍历子模块,而后递归调用 installModule。
因此 modules 的核心实现就在于对当前的 module 的 state 设置了监听,将此段代码提取出来:
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
Vue.set(parentState, moduleName, state || {})
})
复制代码
先猜想 getNestedState 方法能够获取到父 state。因此先取得父 state,再取得当前模块名称,最后使用 Vue.set() 将当前的 state 设置在父 state 下。实际上该实现就是在一个 vue 实例下为 data.state 添加属性,并可以使得 vue 实例可以监听到添加属性的改动。
const parentState = getNestedState(rootState, path.slice(0, -1))
复制代码
经过 path.slice(0, -1) 将当前模块去掉,做为参数和 rootState 根状态传入 getNestedState 方法中,返回了当前模块的父状态 parentState。
来看看 getNestedState 的实现:
function getNestedState (state, path) {
return path.length
? path.reduce((state, key) => state[key], state)
: state
}
复制代码
若是 length 等于 0,即只有根 state,直接返回。另外一种状况,若是有嵌套的模块,那么经过 Array.prototype.reduce() 方法一直往根 state 的属性取 path 对应的 state 并返回。
至此,state 的模块化已经注册完成,而后递归调用 installModule 完成全部 module 的注册。
既然是往 rootState 里添加属性,那么获取则能够经过 store.state.a 来获取到模块,而后再继续获取模块里的 state。
以前在解读 mutation 和 action 的时候,一直都将 getNestedState 这个方法给省略了。在注册 mutation 和 action 的时候,会出现如下这段代码:
getNestedState(store.state, path)
复制代码
实际上这段代码就是获取当前 modules 的 state,而后做为参数回传。
还记得解读 mutation 的时候,说到为何会将 mutation 保存到了 store._mutations 数组里面。主要目的是将全部 module 里的 mutation 都存放在一个数组中,以便于在 commit 的时候能触发全部 mutation。
getter 和 action 用到数组存放也是这样一个缘由。
可是,若是两个 module 里有相同的 mutation 名称,vuex 2.0.0 里作不到只触发其中一个 mutation。这个在日后的版本中设置命名空间
可实现。
本篇是对 module 的一个解读。注册 module 并无想象中的那么复杂,主要分为两个步骤。
第一步是找到当前 module 的父 state,而后在其至少绑定当前 state 的监听,保证修改了 state 会触发相应。
第二步则是递归 module,保证设置子 module 的 state,从而实现 module 的子嵌套。