Open cobish opened 6 years ago
store 将应用的状态集中起来,但如果应用变得非常复杂时,即状态非常的多时,store 就有可能变得相当臃肿。module 能够帮 store 划分了模块,每个模块都拥有自己的 state、getter、mutation、action 和 module。
那么 module 又是怎样进行划分的,划分后的模块又是如何管理自己的状态呢?接下来就来解读 module 的实现吧。
解读前,需要对以下知识有所了解:
在 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。
store.state.count
store.state.a.count
但是示例中的 mutation 则是注册在全局下的,即调用 store.commit('addNote'),将会调用跟模块和 a 模块的 mutation。除非区分各模块 mutation 的命名,否则,在同名的情况下,只要 commit 后就会被触发调用。
store.commit('addNote')
当然,vuex 2.0.0 后面的版本添加了命名空间 的功能,使得 module 更加的模块化。
命名空间
所以接下来要解读的 module 中,实际上只要 state 是被模块化了, action、mutation 和 getter 还是在全局的模块下。
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 的子嵌套。
前言
store 将应用的状态集中起来,但如果应用变得非常复杂时,即状态非常的多时,store 就有可能变得相当臃肿。module 能够帮 store 划分了模块,每个模块都拥有自己的 state、getter、mutation、action 和 module。
那么 module 又是怎样进行划分的,划分后的模块又是如何管理自己的状态呢?接下来就来解读 module 的实现吧。
准备
解读前,需要对以下知识有所了解:
解读
在 vuex 文档里有这么一句话:默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
什么意思呢?先看看以下示例:
使用了 module 之后,state 则会被模块化。比如要调用根模块的 state,则调用
store.state.count
,如果要调用 a 模块的 state,则调用store.state.a.count
。但是示例中的 mutation 则是注册在全局下的,即调用
store.commit('addNote')
,将会调用跟模块和 a 模块的 mutation。除非区分各模块 mutation 的命名,否则,在同名的情况下,只要 commit 后就会被触发调用。当然,vuex 2.0.0 后面的版本添加了
命名空间
的功能,使得 module 更加的模块化。所以接下来要解读的 module 中,实际上只要 state 是被模块化了, action、mutation 和 getter 还是在全局的模块下。
modules 的注册
installModule 里实现了 module 的注册,定位到 installModule 方法。
看到简化后的代码,可以看出 installModule 对 module 做了两步初始化操作。第一步是使用 Vue.set() 对当前的 module 的 state 设置了监听;第二步则是继续遍历子模块,然后递归调用 installModule。
set state
所以 modules 的核心实现就在于对当前的 module 的 state 设置了监听,将此段代码提取出来:
先猜测 getNestedState 方法可以获取到父 state。所以先取得父 state,再取得当前模块名称,最后使用 Vue.set() 将当前的 state 设置在父 state 下。实际上该实现就是在一个 vue 实例下为 data.state 添加属性,并能够使得 vue 实例能够监听到添加属性的改动。
getNestedState
通过 path.slice(0, -1) 将当前模块去掉,作为参数和 rootState 根状态传入 getNestedState 方法中,返回了当前模块的父状态 parentState。
来看看 getNestedState 的实现:
如果 length 等于 0,即只有根 state,直接返回。另一种情况,如果有嵌套的模块,那么通过 Array.prototype.reduce() 方法一直往根 state 的属性取 path 对应的 state 并返回。
至此,state 的模块化已经注册完成,然后递归调用 installModule 完成所有 module 的注册。
既然是往 rootState 里添加属性,那么获取则可以通过 store.state.a 来获取到模块,然后再继续获取模块里的 state。
get modules state
之前在解读 mutation 和 action 的时候,一直都将 getNestedState 这个方法给省略了。在注册 mutation 和 action 的时候,会出现以下这段代码:
实际上这段代码就是获取当前 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 的子嵌套。