书接上文:手写 Vuex (一) 中,实现了 Vuex 的基础功能,现在继续对其进行完善,实现模块化的状态管理。模块化可以帮助我们更好地组织和管理复杂的应用状态,使得状态的结构更加清晰和可维护。
格式化参数
将参数模块格式化为模块嵌套的树形结构(如下),方便我们后续的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| this.root = { _raw: xxx, _children: { a模块: { _raw: xxx, _children: {}, state: xxx.state }, b模块: { _raw: xxx, _children: {}, state: xxx.state } }, state: xxx.state }
|
Module 类
创建 Module 类,通过 new Module 便可以得到格式化后的树形数据结构。
1 2 3 4 5 6 7 8
| export default class Module { constructor(rootModule) { this._raw = rootModule, this._children = {} this.state = rootModule.state } }
|
ModuleCollection 类
在这个类中,实现将用户传入的参数转化为格式化后的结果,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import forEachValue from "./utils"; import Module from './module.js' export default class ModlueCollection { constructor(options) { this.register([], options) } register(path, rootModule) { const newModlue = new Module(rootModule)
if (path.length == 0) { this.root = newModlue } else { const parent = path.slice(0, -1).reduce((pre, next) => { return pre.getChild(next) }, this.root) parent.addChild(path[path.length - 1], newModlue) } if (rootModule.modules) { forEachValue(rootModule.modules, (moduleValue, moduleName) => { this.register([...path, moduleName], moduleValue) }) } } }
|
forEachValue
其中 forEachValue 方法提取为工具方法,方便后续复用
1 2 3 4 5 6
| const forEachValue = (obj = {}, fn) => { Object.keys(obj).forEach(key => { fn(obj[key], key) }) } export default forEachValue
|
getChild、addChild
增加获取子模块和追加子模块方法,便于调用
1 2 3 4 5 6 7 8 9 10 11 12
| export default class Module { ... getChild(key) { return this._children[key] } addChild(key, module) { this._children[key] = module } }
|
至此完成模块格式化为模块嵌套的树形结构,接下来重构 Store,实现 state、getter、commit、dispatch等
installModule
installModlue方法:将创建的树形结构上的状态、方法安装到 Store 实例上,就可以通过$store方式获取到对应的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import ModuleCollection from './module-collection.js' import forEachValue from './utils' let Vue function installModule(store, rootState, path, module) { if (path.length > 0) { let parent = path.slice(0, -1).reduce((pre, next) => { return pre[next] }, rootState) Vue.set(parent, path[path.length - 1], module.state) } module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child) }) } class Store { constructor(options) { this._modules = new ModuleCollection(options) const state = this._modules.root.state installModule(this, state, [], this._modules.root) } }
|
forEachChild
遍历安装当前模块的子模块
1 2 3 4 5 6 7 8
| export default class Module { ... forEachChild(fn) { forEachValue(this._children, fn) } }
|
resetStoreVm
实现 state 数据响应式响应式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| function resetStoreVm(store, state) { const wrappedGetters = store._wrappedGetters const computed = {} store.getters = Object.create(null) forEachValue(wrappedGetters, (fn, key) => { computed[key] = () => { return fn() } Object.defineProperty(store.getters, key, { get: () => { return store._vm[key] } }) }) store._vm = new Vue({ data() { return { $$state: state } }, computed }) }
class Store { constructor(options) { ... let state = this._modules.root.state ... resetStoreVm(this, state) }
get state() { return this._vm._data.$$state } ... }
|
forEachGetters
扩展 Module 类,遍历 getters
1 2 3 4 5 6 7 8 9 10 11
| export default class Module { ... forEachGetters(fn) { if (this._raw.getters) { forEachValue(this._raw.getters, fn) } }
}
|
getter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function installModule(store, rootState, path, module) { ... module.forEachGetters((getters, key) => { store._wrappedGetters[key] = () => { return getters(module.state) } }) ... }
class Store { constructor(options) { ... this._wrappedGetters = Object.create(null) ... } }
|
commit(mutations)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function installModule(store, rootState, path, module) { ... module.forEachMutations((mutations, type) => { store._mutations[type] = (store._mutations[type] || []) store._mutations[type].push((payload) => { mutations.call(store, module.state, payload) }) }) ... }
class Store { constructor(options) { ... this._mutations = Object.create(null) ... } commit = (type, payload) => { this._mutations[type].forEach(fn => fn(payload)) } }
|
forEachMutations
1 2 3 4 5 6 7 8 9 10 11
| export default class Module { ... forEachMutations(fn) { if (this._raw.mutations) { forEachValue(this._raw.mutations, fn) } }
}
|
dispatch(actions)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function installModule(store, rootState, path, module) { ... module.forEachActions((actions, type) => { store._actions[type] = (store._actions[type] || []) store._actions[type].push((payload) => { actions.call(store, store, payload) }) }) ... }
class Store { constructor(options) { ... this._actions = Object.create(null) ... } dispatch = (type, payload) => { this._actions[type].forEach(fn => fn(payload)) } }
|
forEachActions
1 2 3 4 5 6 7 8 9 10 11
| export default class Module { ... forEachActions(fn) { if (this._raw.actions) { forEachValue(this._raw.actions, fn) } }
}
|
namespace
默认情况下(或 namespace: false),模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果想要模块具有更高的封装度和复用性,可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
getNamespaced
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export default class ModlueCollection { ... getNamespaced(path) { let root = this.root return path.reduce((pre, next)=>{ root = root.getChild(next) return pre + (root.namespaced ? next + '/' :'') }, '') } }
|
1 2 3 4 5 6 7
| export default class Module { ... get namespaced () { return this._raw.namespaced } }
|
添加 namespace
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function installModule(store, rootState, path, module) { const namespaced = store._modules.getNamespaced(path) module.forEachMutations((mutations, type) => { store._mutations[namespaced + type] = (store._mutations[namespaced + type] || []) store._mutations[namespaced + type].push((payload) => { mutations.call(store, module.state, payload) }) }) module.forEachActions((actions, type) => { store._actions[namespaced + type] = (store._actions[namespaced + type] || []) store._actions[namespaced + type].push((payload) => { actions.call(store, store, payload) }) }) module.forEachGetters((getters, key) => { store._wrappedGetters[namespaced + key] = () => { return getters(module.state) } }) ... }
|
严格模式
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
接收 strict 配置项,添加 mutation 提交状态标识
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Store { constructor(options) { ... const { strict = false } = options this.strict = strict this._committing = false resetStoreVm(this, state) } _withCommit = (fn) => { var committing = this._committing; this._committing = true; fn(); this._committing = committing; } commit = (type, payload) => { if (!this._mutations[type]) { throw new Error(`Mutation "${type}" not found`); } const entry = this._mutations[type] this._withCommit(() => { entry.forEach((handler) => { handler(payload) }) }) } } function resetStoreVm(store, state) { ... if (store.strict) { enableStrictMode(store) } }
|
enableStrictMode
监听 state 数据变化,判断 _committing 如果是 true 表示是同步执行,如果为 false,则会抛出错误提示
1 2 3 4 5
| function enableStrictMode(store) { store._vm.$watch(function () { return this._data.$$state }, () => { console.assert(store._committing, `[vuex] do not mutate vuex store state outside mutation handlers.`) }, { deep: true, sync: true }) }
|
以上只是基于vuex源码实现了最核心的功能,但它帮助我们更好地理解了 Vuex 的模块化的实现原理。本来计划在这篇中实现 map辅助函数,但发现模块化的写起来实在太多了,所以将 mapState、 mapGetters、 mapMutations、 mapActions 放到下篇讲解。希望这篇文章对你有所帮助!更多详细信息请参考 vuex 源码。
系列文章