各位观众老爷们,晚上好!我是老码,今天咱们聊聊 Vuex 源码里那些弯弯绕绕,特别是关于 Module 的注册和 namespaced 的实现。这部分内容,说白了,就是把你的 Vuex store 划分成一个个小格子,每个格子都有自己的地盘,互不干扰。
准备好了吗?咱们这就开始!
第一章:从 Store 的初始化说起:installModule 的调用
要理解 Module 的注册,必须先看看 Store 是怎么初始化的。在 Vuex 的核心代码里,Store 的构造函数会调用一个叫做 installModule 的方法,这个方法就是负责把你的模块一个一个注册到 Vuex store 里的。
// 简化后的 Store 构造函数
class Store {
constructor (options = {}) {
// ... 省略其他初始化代码 ...
// 根模块
this._modules = new ModuleCollection(options);
// ... 省略其他代码 ...
// 安装模块
installModule(this, this.state, [], this._modules.root);
// ... 省略其他代码 ...
}
}
看到了吗?installModule 被调用了!它接收四个参数:
this: 指向当前的Store实例。this.state: 指向 Vuex store 的根状态。[]: 一个空的路径数组,表示当前正在安装根模块。this._modules.root: 指向根模块的Module实例。
ModuleCollection 是干什么的?简单来说,它负责把你的 Vuex options 里的 modules 选项,转换成一个树状结构,方便后续的模块注册。
第二章:installModule 的真面目:模块注册的核心逻辑
installModule 是个递归函数,它会遍历模块树,把每个模块都注册到 Vuex store 里。咱们来看看它的简化版代码:
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length; // 是否是根模块
const namespaced = store._modules.getNamespace(path); // 获取命名空间
// 1. 注册 mutations, actions, getters
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1)); // 获取父模块的状态
// 注册子模块的状态
Vue.set(parentState, path[path.length - 1], module.state);
}
// 注册 mutations
module.forEachMutation((mutation, key) => {
const namespacedType = namespaced + key;
registerMutation(store, namespacedType, mutation, module);
});
// 注册 actions
module.forEachAction((action, key) => {
const type = action.root ? key : namespaced + key; // root action 不需要命名空间
const handler = action.handler || action;
registerAction(store, type, handler, module);
});
// 注册 getters
module.forEachGetter((getter, key) => {
const namespacedType = namespaced + key;
registerGetter(store, namespacedType, getter, module);
});
// 2. 递归安装子模块
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot);
});
// 3. 注册 watch
if (module.namespaced) {
store._devtoolHook && store._devtoolHook.emit('vuex:module:add', path, module);
}
}
这段代码看起来有点长,但其实逻辑很清晰:
- 注册 mutations, actions, getters: 遍历模块的 mutations, actions, getters,然后调用
registerMutation,registerAction,registerGetter函数,把它们注册到 Vuex store 的内部状态里。注意,这里会根据namespaced的值,给 mutations, actions, getters 加上命名空间。 - 递归安装子模块: 遍历模块的子模块,然后递归调用
installModule函数,把子模块也注册到 Vuex store 里。 - 注册 watch: 如果模块是命名空间的,就通知 Vue Devtools,方便调试。
第三章:命名空间的奥秘:namespaced 属性的作用
namespaced 属性是 Vuex 模块里一个很重要的属性。它决定了模块的 mutations, actions, getters 是否要加上命名空间。
const moduleA = {
namespaced: true,
state: () => ({ count: 0 }),
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment ({ commit }) {
commit('increment')
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
如果 namespaced 为 true,那么:
- mutation
increment的类型会变成moduleA/increment。 - action
increment的类型会变成moduleA/increment。 - getter
doubleCount的访问路径会变成moduleA/doubleCount。
这样做的好处是:
- 避免命名冲突: 不同的模块可以使用相同的 mutation, action, getter 名称,而不会发生冲突。
- 提高代码可维护性: 模块之间的依赖关系更加清晰,代码更容易理解和维护。
第四章:registerMutation, registerAction, registerGetter 的实现细节
这三个函数负责把 mutations, actions, getters 注册到 Vuex store 的内部状态里。咱们来看看它们的简化版代码:
function registerMutation (store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = []);
entry.push(function wrappedMutationHandler (payload) {
handler.call(local, store.state, payload)
})
}
function registerAction (store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = []);
entry.push(function wrappedActionHandler (payload) {
let res = handler.call(local, {
dispatch: store.dispatch,
commit: store.commit,
getters: store.getters,
state: store.state,
rootState: store.state
}, payload)
if (!isPromise(res)) {
res = Promise.resolve(res)
}
return res
})
}
function registerGetter (store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
console.error(`[vuex] duplicate getter key: ${type}`)
return
}
store._wrappedGetters[type] = function wrappedGetter (storeState, storeGetters) {
return rawGetter.call(local,
local.state, // local state
local.getters, // local getters
storeState, // root state
storeGetters // root getters
)
}
}
这三个函数做的事情很简单:
registerMutation: 把 mutation handler 包装成一个函数,然后添加到store._mutations对象里。registerAction: 把 action handler 包装成一个函数,然后添加到store._actions对象里。Action 包装器中,向 action 注入dispatch,commit,getters,state,rootState等上下文对象,方便在 action 中进行状态修改和派发其他 action。registerGetter: 把 getter 函数包装成一个函数,然后添加到store._wrappedGetters对象里。 Getter 包装器中,处理了局部 state/getters 和全局 state/getters 的注入,使得 getter 可以访问到不同层级的数据。
第五章:ModuleCollection 的作用:构建模块树
ModuleCollection 负责把你的 Vuex options 里的 modules 选项,转换成一个树状结构。咱们来看看它的简化版代码:
class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
register (path, rawModule, runtime) {
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
getNamespace (path) {
let module = this.root
return path.reduce((namespace, key) => {
module = module.getChild(key)
return namespace + (module.namespaced ? key + '/' : '')
}, '')
}
get (path) {
return path.reduce((module, key) => {
return module.getChild(key)
}, this.root)
}
}
这段代码主要做了三件事:
register: 注册模块。它会创建一个Module实例,然后把它添加到模块树里。getNamespace: 获取模块的命名空间。它会遍历模块树,把每个模块的命名空间拼接起来。get: 根据路径获取模块。
第六章:Module 类的实现:存储模块的信息
Module 类负责存储模块的信息,比如 state, mutations, actions, getters, children 等。咱们来看看它的简化版代码:
class Module {
constructor (rawModule, runtime) {
this.runtime = runtime
this._children = Object.create(null)
this._rawModule = rawModule
const rawState = rawModule.state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
this.namespaced = !!rawModule.namespaced
this._mutations = Object.create(null)
this._actions = Object.create(null)
this._getters = Object.create(null)
forEachValue(rawModule.mutations, (mutation, key) => {
this._mutations[key] = mutation
})
forEachValue(rawModule.actions, (action, key) => {
this._actions[key] = action
})
forEachValue(rawModule.getters, (getter, key) => {
this._getters[key] = getter
})
}
getChild (key) {
return this._children[key]
}
addChild (key, module) {
this._children[key] = module
}
forEachMutation (fn) {
forEachValue(this._mutations, fn)
}
forEachAction (fn) {
forEachValue(this._actions, fn)
}
forEachGetter (fn) {
forEachValue(this._getters, fn)
}
forEachChild (fn) {
forEachValue(this._children, fn)
}
}
这段代码主要做了这些事情:
- 构造函数: 初始化模块的 state, mutations, actions, getters, children 等属性。
getChild,addChild: 用于访问和添加子模块。forEachMutation,forEachAction,forEachGetter,forEachChild: 用于遍历模块的 mutations, actions, getters, children。
第七章:总结:模块注册和命名空间的流程
咱们来总结一下 Vuex 模块注册和命名空间的流程:
Store构造函数调用installModule函数,开始注册模块。installModule函数会递归遍历模块树,把每个模块都注册到 Vuex store 里。- 对于每个模块,
installModule函数会:- 注册 mutations, actions, getters,并根据
namespaced属性,给它们加上命名空间。 - 递归安装子模块。
- 注册 mutations, actions, getters,并根据
ModuleCollection负责把 Vuex options 里的modules选项,转换成一个树状结构,方便后续的模块注册。Module类负责存储模块的信息,比如 state, mutations, actions, getters, children 等。
为了更直观地理解,咱们用一个表格来总结一下:
| 步骤 | 负责的函数/类 | 主要作用
| Store 构造函数 | 调用 installModule 函数,启动模块注册流程。