书接上回, 上回书说了 vuex 的安装以及 store 构造函数, 下面我们来讲后面部分
收集 modules
vuex 允许我们自定义多个模块, 防止应用的所有状态会集中到一个比较大的对象, 导致 store 就变的臃肿了
- const moduleA = {
- state: { ... },
- mutations: { ... },
- actions: { ... },
- getters: { ... }
- }
- const moduleB = {
- state: { ... },
- mutations: { ... },
- actions: { ... }
- }
- const store = new Vuex.Store({
- modules: {
- a: moduleA,
- b: moduleB
- }
- })
- store.state.a // -> moduleA 的状态
- store.state.b // -> moduleB 的状态
这是如何实现的呢?
在 store 的构造函数里面有这样一段代码:
this._modules = new ModuleCollection(options)
, 他就是用来收集用户自定义的 modules, 该函数位于
module/module-collection.js
下
现在假设我们的 modules 做如下设置:
- store = {
- modules: {
- moduleA: {
- state: {},
- modules: {
- moduleC: {
- state: {},
- actions: {}
- }
- }
- },
- modulesB: {
- state: {},
- mutations: {
- // xxx
- }
- }
- }
- }
模块关系图如下:
来到 ModuleCollection 的构造函数, 很简单, 调用其 register 方法, 并传入三个参数
- register(path, rawModule, runtime = true) {
- if (process.env.NODE_ENV !== 'production') {
- assertRawModule(path, rawModule)
- }
- const newModule = new Module(rawModule, runtime)
- if (path.length === 0) {
- // 整个传入 store 里的 option 为一个大的 module ,
- // 再对 option 里的 modules 属性进行递归注册为 module
- 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) => {
- // 这里的 concat 并不改变原来的数组, 所以如果是同级的 module , 那么他还是有着相同的父级
- this.register(path.concat(key), rawChildModule, runtime)
- })
- }
- }
现在我们一步一步来分析 先讲一下 register 里的对传入模块结构的断言, 调用
assertRawModule(path, rawModule)
以确保
getters/mutations/actions
依次符合
函数 / 函数 /(对象或者函数)
的结构, 这部分代码并不难, 但是作者的这种编码习惯非常值得学习, 详见 assertRawModule
回到正题
第一次 调用
this.register([], rawRootModule, false)
此时传入 register 函数的 path 为 空数组, rawModule 为最外层的 store 对象, 即可以理解为 根 module,runtime 为 false
接着调用
new Module(rawModule, runtime)
实例化这个 根 module
- constructor (rawModule, runtime) {
- this.runtime = runtime
- // 存储子 modules
- this._children = Object.create(null)
- // Store the origin module object which passed by programmer
- // 存储原始的这个传进来的 module
- this._rawModule = rawModule
- const rawState = rawModule.state // 获取模块的 state
- // Store the origin module's state
- this.state = (typeof rawState === 'function' ? rawState() : rawState) || {} // state 最终是一个对象
- }
回到 register 函数, 如果此时的 path 为 空数组, 那么就将此模块设置为 整个状态树的 根模块, 即
this.root= 根 module
, 这里我们的 path=[], 所以它是 根 module, 不走 else
来到后面, 判断该模块是否有 modules 属性, 即有子模块, 有则继续循环注册子模块, 我们这里有 moduleA 和 moduleB , 所以继续注册
- // util.js
- function forEachValue (obj, fn) {
- Object.keys(obj).forEach(key => fn(obj[key], key))
- }
第二次调用
this.register(path.concat(key), rawChildModule, runtime)
此时传入 register 函数的 path 为 path.concat(key), 即 path =['moduleA'] ,rawModule 为 moduleA 对象, runtime 为 false
注: path.concat(key) 并不改变原始的 path, 它返回一个新的数组, 所以
根 module 的 path 栈
还是空, 这一点很重要
继续重复第一步的步骤, 不同的是, 实例化完 moduleA 后, 由于此时的 path =['moduleA'], 所以它走 else
- else {
- const parent = this.get(path.slice(0, -1))
- parent.addChild(path[path.length - 1], newModule)
- }
path.slice(0, -1) 返回 path 数组以外的其他元素, 不改变原始数组, 所以等价于 this.get([])
- // 作用: 获取当前的模块的父模块
- // 传入的 path 是从根模块到父模块的一条链路
- get(path) {
- return path.reduce((module, key) => {
- return module.getChild(key)
- }, this.root)
- }
this.root 为前面的 根 module, 而 path 是空, 所以 parent = 根 module , 然后执行
parent.addChild(path[path.length - 1], newModule)
, 此时获取 path 栈顶元素 ("moduleA") 作为 key , 和 实例 moduleA 作为 value , 加入到 父模块 (根 module) 的子元素对象中
由于 moduleA 还有子模块, 所以继续递归 子模块
第三次调用
this.register(path.concat(key), rawChildModule, runtime)
此时传入 register 函数的 path 为 path.concat(key), 即 path =['moduleA'] ,rawModule 为 moduleA 对象, runtime 为 false
继续上面步骤 来到 this.get, 这是传入的参数是 ['moduleA'], 即 moduleC 的父模块 moduleA 由于 根 module 保存了 moduleA, 所以通过这种类似于链的方式来获取 父模块, 同理将 moduleC 加入 moduleA 的子模块对象中 至此, 第一条链就讲完了,
返回到 根 module 的 forEachValue 循环中, 这里我们讲到, 他的 path 还是空, 这就体现了 使用 concat 方法的好处与机智 所以与处理 moduleA 的过程一模一样
第四次调用
this.register(path.concat(key), rawChildModule, runtime)
此时传入 register 函数的 path 为 path.concat(key), 即 path =['moduleB'] ,rawModule 为 moduleB 对象, runtime 为 false
终于将
this._modules = new ModuleCollection(options)
的过程分析完毕了 最终的
this._modules.root(不包括方法)
如下图所示
总的看下来挺佩服作者的思维以及处理方式的
看着挺长的了, 其实就是多了几个循环过程的讲解, 所以要不要再翻篇呢? 呢? 呢?????
回到
store.js 的构造函数
- ,
- const state = this._modules.root.state // 将 "根模块对象的 state" (即最外层 store 的 state 对象)赋予 state ,
- installModule(this, state, [], this._modules.root)
初始化根模块, 并且递归注册子模块, 并且收集所有模块的 getters
- function installModule(store, rootState, path, module, hot) {
- // hot 当动态改变 modules 或者热更新的时候为 true.
- const isRoot = !path.length // 判断是否是根模块
- const namespace = store._modules.getNamespace(path) // 获取 s 使用的命名空间
- // register in namespace map
- if (module.namespaced) {
- // 如果命名空间存在, 就在 store 对象中建立 namespace 到模块的映射
- store._modulesNamespaceMap[namespace] = module
- }
- // set state
- if (!isRoot && !hot) { // 如果不是根模块以及 hot = false, 这里我们是根模块, 所以我们先放一放, 跳到下一步
- const parentState = getNestedState(rootState, path.slice(0, -1))
- const moduleName = path[path.length - 1]
- store._withCommit(() => {
- Vue.set(parentState, moduleName, module.state)
- })
- }
- // 跳到下面先看看 makeLocalContext,
- // 又跳!!! 放心,, 不用跳多远, 也就是下一个 issue,,, 抠鼻. gif
- const local = module.context = makeLocalContext(store, namespace, path)
- // 注册各个模块的 mutaations 方法到 store._mutations 中, 每个 type 对应一个数组
- module.forEachMutation((mutation, key) => {
- const namespacedType = namespace + key
- registerMutation(store, namespacedType, mutation, local)
- })
- // 注册各个模块的 actions 到 store._actions
- module.forEachAction((action, key) => {
- const type = action.root ? key : namespace + key
- const handler = action.handler || action
- registerAction(store, type, handler, local)
- })
- // 注册各个模块的 getters 到 store._wrappedGetters
- module.forEachGetter((getter, key) => {
- const namespacedType = namespace + key
- registerGetter(store, namespacedType, getter, local)
- })
- module.forEachChild((child, key) => { // 递归子模块
- installModule(store, rootState, path.concat(key), child, hot)
- })
- }
上面出现的有关函数由于排版以及篇幅原因, 我放到了 我看 Vue(三) 中
总结下, installModule 都干了些神马:
获取命名空间
获取本模块的 context 属性, 里面包含了本模块的
dispatch/commit/getter/state
等属性或方法
将 各个模块按照命名空间将
mutations/getters/actions
加入到全局的
_mutations /_wrappedGetters/_actions
里
看完上面的代码, 我有些疑问, 这个 store.state 和 store.getter 是哪来的?
- resetStoreVM(this, state)
- function resetStoreVM(store, state, hot) {
- const oldVm = store._vm // 之前的 vue 实例
- // bind store public getters
- store.getters = {} // 终于找到你
- const wrappedGetters = store._wrappedGetters // 前面说过的各个模块的 getters 集合
- const computed = {}
- forEachValue(wrappedGetters, (fn, key) = >{
- // use computed to leverage its lazy-caching mechanism
- computed[key] = () = >fn(store) // 收集各个 getter, 等会传入 computed , 以此做到响应式
- Object.defineProperty(store.getters, key, {
- get: () = >store._vm[key],
- // 因为利用了计算属性, 所以各个 getter 就变成了 vue 实例的属性
- enumerable: true // for local getters
- })
- })
- // use a Vue instance to store the state tree
- // suppress warnings just in case the user has added
- // some funky global mixins
- const silent = Vue.config.silent //// 是否取消 Vue 所有的日志与警告
- Vue.config.silent = true
- // 重新 new
- store._vm = new Vue({
- data: {
- $state: state // 这里虽然是 $state, 但是利用 store.state 时获取的就是它
- },
- computed
- })
- /* get state() {
- return this._vm._data.$state
- }*/
- Vue.config.silent = silent
- // enable strict mode for new vm
- if (store.strict) {
- enableStrictMode(store)
- }
- if (oldVm) {
- if (hot) {
- // 解除旧 vm 的 state 的引用, 以及销毁旧的 Vue 对象
- // dispatch changes in all subscribed watchers
- // to force getter re-evaluation for hot reloading.
- store._withCommit(() = >{
- oldVm._data.$state = null
- })
- }
- Vue.nextTick(() = >oldVm.$destroy())
- }
- }
解决了 store.getter 了, 那么 store.state 是如何来的呢? 还记不记得第一次让你跳的地方, 没错就是 installModule
- if (!isRoot && !hot) { // 如果不是根模块以及 hot = false, 这里我们是根模块, 所以我们先放一放, 跳到下一步
- const parentState = getNestedState(rootState, path.slice(0, -1))
- const moduleName = path[path.length - 1]
- store._withCommit(() => {
- Vue.set(parentState, moduleName, module.state)
- })
- }
这里我们就来看
Vue.set(parentState, moduleName, module.state)
它的作用是在父模块的 state 属性上添加上本模块的 state, 还是按照一开始我们那种依赖关系来看:
这样我们就不难理解 getNestState 里面为什么可以如此获取 state 了
到此, vuex 核心内容大致为此了, 后面在 我看 Vuex(三) 中我会继续学习下其他一些函数的妙用
来源: https://juejin.im/post/5a8eb24e6fb9a0633c66209b