解读完 module 以后,我的以为有了 namespaced
的 module 才算是真正的模块,因而又补充了这一篇。vue
namespaced 把 getter、mutation 和 action 都作了真正的模块化,使得 store 可使用特定模块的 mutation 等。本篇就来瞧瞧 namespaced 是如何实现的?git
这一次阅读的是 vuex 的 2.1.0
版本,源码请戳 这里。建议下载并打开代码跟着解读,不然可能看得一脸懵逼。github
vuex 2.1.0 的代码大体与 2.0.0 相似,只不过将 module 提取到一个目录里。里面有两个文件,分别是 module-collection.js 和 module.js,接下来会涉及到这两个文件的解读。vuex
仍是从 store 的构造函数 constructor 出发,开始寻找与 module 和 namespaced 相关的代码。数组
constructor (options = {}) {
this._modules = new ModuleCollection(options)
this._modulesNamespaceMap = Object.create(null)
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
}
复制代码
咱们先把 _modulesNamespaceMap
单独拎出来,这个属性主要是辅助函数 mapState 使用到,解读到 mapState 时再用它。app
因此 constructor 对 module 作了两件事,一是经过传配置参数 options 来初始化 _modules,二是经过 installModule 来注册 module。ide
作的事情很少,但里面实现的代码还真很多,你们作好心理准备吧哈哈。模块化
首先来看看 new ModuleCollection 初始化 _modules 到底作了哪些事。定位到 module-collection.js
文件,看到它的构造函数:函数
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.root = new Module(rawRootModule, false)
// register all nested modules
if (rawRootModule.modules) {
forEachValue(rawRootModule.modules, (rawModule, key) => {
this.register([key], rawModule, false)
})
}
}
复制代码
构造函数作了也是两件事情,一件是注册了一个根 module,另外一个是遍历注册子 module。打开 module.js
看下主要使用到的代码:ui
export default class Module {
constructor (rawModule, runtime) {
this.runtime = runtime
this._children = Object.create(null)
this._rawModule = rawModule
}
addChild (key, module) {
this._children[key] = module
}
getChild (key) {
return this._children[key]
}
}
复制代码
构造函数里添加 _children 即子模块,而后当前模块保存在 _rawModule。而后就是两个会用到的方法 addChild 和 getChild,顾名思义,就是添加子模块和获取子模块会用到。
再回到 ModuleCollection 构造函数的第二步,定位到 register
方法:
// 获得对应 path 的 module
get (path) {
return path.reduce((module, key) => {
return module.getChild(key)
}, this.root)
}
register (path, rawModule, runtime = true) {
// path.slice(0, -1) 表示去掉最后一个
// 取得父 module
const parent = this.get(path.slice(0, -1))
// new 一个新的 module
const newModule = new Module(rawModule, runtime)
// 添加子 module
parent.addChild(path[path.length - 1], newModule)
// register nested modules
if (rawModule.modules) {
// 递归注册子 module
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
复制代码
代码注释都添加上了,能够看到该 register 跟以前解读 module 时递归 set state 有点相似。这里递归完后会生成一个 module 实例,若该实例有子 module,那么存放在它的 _children 属性中,以此类推。
store 初始化 _modules 属性后,接下来就是注册 module。定位到 installModule 方法:
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (namespace) {
store._modulesNamespaceMap[namespace] = 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, module.state)
})
}
const local = module.context = makeLocalContext(store, namespace)
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, path)
})
module.forEachAction((action, key) => {
const namespacedType = namespace + key
registerAction(store, namespacedType, action, local, path)
})
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local, path)
})
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
复制代码
代码虽然有点长,但有些是之前解读过的(注册 state),有些是大同小异的(注册 mutation、action 和 getter)。大体瞄了一眼,其中根据 _modules 生成 namespace,而后分别注册 state、mutation、action 和 getter,最后递归注册子模块。
由于注册 state 和递归子模块以前解决过,因此再也不重复。接下就是看看 getNamespace 和 registerAction(其它大同小异) 是如何实现的。
定位到 ModuleCollection 的 getNamespace:
getNamespace (path) {
let module = this.root
return path.reduce((namespace, key) => {
module = module.getChild(key)
return namespace + (module.namespaced ? key + '/' : '')
}, '')
}
复制代码
根据参数 path 来拼接 namespace。好比如下:
// options
{
modules: {
a: {
modules: {
b: {
// ...
}
}
}
}
}
复制代码
path 数组对应的 namespace 分别为:
// []
/
// [a]
/a/
// [a, b]
/a/b/
复制代码
获取到 namespace 以后,接下来就是注册 action。
const local = module.context = makeLocalContext(store, namespace)
module.forEachAction((action, key) => {
const namespacedType = namespace + key
registerAction(store, namespacedType, action, local, path)
})
复制代码
第二步的 registerAction 以前已经解读过,只不过是将子 module 里的 action 用 namespacedType 做为 key 表示,用来区分 store._actions 的 key。因此这段代码主要解读第一步的 makeLocalContext。
function makeLocalContext (store, namespace) {
const noNamespace = namespace === ''
const local = {
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if (!options || !options.root) {
type = namespace + type
if (!store._actions[type]) {
console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
return
}
}
return store.dispatch(type, payload)
}
}
return local
}
复制代码
代码简化后仍是很多的。其中 unifyObjectStyle 方法只是为了支持传参有多种格式,这里再也不详细解读。
这里主要是为了 action 传回来的参数后再 dispatch action 时简化第一个参数的路径。看如下代码可能会比较容易理解:
modules: {
foo: {
namespaced: true,
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们能够接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
}
}
}
}
复制代码
vuex 的辅助函数有带命名空间的绑定函数,以下:
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo',
'bar'
])
}
复制代码
mapActions 方面,主要在原来的实现上包装了一层 normalizeNamespace。打开 helper.js 文件,找到 normalizeNamespace 方法:
function normalizeNamespace (fn) {
return (namespace, map) => {
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/'
}
return fn(namespace, map)
}
}
复制代码
主要是拼接 path 给 store.__actions 调用。
可是 state 不像 action 是存放在数组里的,因此须要用到 _modulesNamespaceMap 来取得当前的 module,才能取到里面的 state。
_modulesNamespaceMap 具体实现代码很少,就不详细解读了。
终于将 namespace 解读完了,感受比以前的解读要困难一些,涉及到的代码量也多了很多,因此有些代码也没能详细解读。
module 添加了 namespace,将整个 module 都提取了出来,递归初始化一个 _modules,方便后面模块的查找与使用。
namespace 做为路径,并做为数组的 key 去访问到子模块的 action 等。从而能够单独访问到子模块内的 action 等。相比于以前只能访问子模块内 state 而不能访问 action 等的 2.0.0 版本,2.1.0 版本添加了 namespace 就更加模块化了。