手写vuex4源码(四)模块的状态的实现

时间:2021-10-28 01:16:40

一、模块收集

1、模块的树形结构-模型树

Vuex 的模块,理论上是支持无限层级递归的树型结构;

但当前版本的 Vuex 仅仅处理了单层 options 对象;

因此,需要继续添加支持深层处理的递归逻辑,从而完成“模块树”的构建,即:实现 Vuex 的模块收集;

为了能够模拟出多层级的“模块树”,我们再创建一个模块 ModuleC,并注册到 ModuleA 下;

模块层级设计:index 根模块中包含模块 A、模块 B;模块 A 中,又包含了模块 C;

modules: { // 子模块
    aCount: {
      namespaced: true,
      state: { count: 0 },
      mutations: {
        add(state, payload) { // aCount/add
          state.count += payload
        }
      },
      modules: {
          cCount: {
              namespaced:true,
              state: { count: 0 },
              mutations: {
                  add(state, payload) { // aCount/cCount
                      state.count += payload
                  }
              },
          }
      }
    },
    bCount: {
      state: { count: 0 },
      namespaced: true,
      mutations: {
        add(state, payload) {
          state.count += payload
        }
      },
    }
  }

2、模块收集的逻辑

我们期望最后实现的结构如下

{
  _raw: '根模块',
  _children:{
    moduleA:{
      _raw:"模块A",
      _children:{
        moduleC:{
          _raw:"模块C",
          _children:{},
          state:'模块C的状态'  
        }
    	},
    	state:'模块A的状态'  
    },
    moduleB:{
      _raw:"模块B",
      _children:{},
      state:'模块B的状态'  
    }
  },
  state:'根模块的状态'
}

3、模块收集的实现

在vuex下创建Modules文件夹,创建module-collection.js文件,创建 ModuleCollection 类:用于在 Vuex 初始化时进行模块收集操作;

export default class ModuleCollection {}

模块收集的操作,就是(深度优先)递归地处理 options 选项中的 modules 模块,构建成为树型结构:

创建 register 方法:携带模块路径,对当前模块进行注册,执行“模块树”对象的构建逻辑;

register(rawModule, path) {
        const newModule = new Module(rawModule);
        // rawModule.newModule = newModule; // 把新的模块 添加到原始对象上
        if (path.length == 0) { // 是一个根模块
            this.root = newModule
        } else {
         /**
             * 当path为['aCount','cCount']时,path.slice(0, -1)为aCount,截取掉数组的最后一个
             * reduce中的current为aCount,module为传入的this.root
             * 第一次aCount时,将aCount赋值给了this.root,所以在此处的parent一定是子元素的父元素
             */
            const parent = path.slice(0, -1).reduce((module, current) => {
                return module.getChild(current)
            }, this.root);
            parent.addChild(path[path.length - 1], newModule)
        }
        if (rawModule.modules) {
            forEachValue(rawModule.modules, (rawChildModule, key) => {
                this.register(rawChildModule, path.concat(key))
            })
        }
        console.log(newModule)
        return newModule
    }

手写vuex4源码(四)模块的状态的实现

模块安装

递归当前树中的state、getter、mutation、action 定义到当前 store 实例中
在 Store 类中,创建 installModule 模块安装方法:对当前模块对象进行递归处理;
Module 类:src/vuex/modules/module.js

// src/vuex/modules/module.js

/**
 * Module 模块类,提供模块数据结构与相关能力扩展
 */
class Module {
  constructor(newModule) {
    this._raw = newModule;
    this._children = {};
    this.state = newModule.state
  }
  /**
   * 根据模块名获取模块实例
   * @param {*} key 模块名
   * @returns 模块实例
   */
  getChild(key) {
    return this._children[key];
  }
  /**
   * 向当前模块实例添加子模块
   * @param {*} key     模块名
   * @param {*} module  子模块实例
   */
  addChild(key, module) {
    this._children[key] = module
  }

  // 基于 Module 类,为模块扩展其他能力...

  /**
   * 遍历当前模块下的 mutations,具体处理由外部回调实现
   * @param {*} fn 返回当前 mutation 和 key,具体处理逻辑由调用方实现
   */
  forEachMutation(fn) {
    if (this._raw.mutations) {
      Object.keys(this._raw.mutations).forEach(key=>fn(this._raw.mutations[key],key));
    }
  }
  /**
   * 遍历当前模块下的 actions,具体处理由外部回调实现
   * @param {*} fn 返回当前 action 和 key,具体处理逻辑由调用方实现
   */
  forEachAction(fn) {
    if (this._raw.actions) {
      Object.keys(this._raw.actions).forEach(key=>fn(this._raw.actions[key],key));
    }
  }
  /**
   * 遍历当前模块下的 getters,具体处理由外部回调实现
   * @param {*} fn 返回当前 getter 和 key,具体处理逻辑由调用方实现
   */
  forEachGetter(fn) {
    if (this._raw.getters) {
      Object.keys(this._raw.getters).forEach(key=>fn(this._raw.getters[key],key));
    }
  }
  /**
   * 遍历当前模块的子模块,具体处理由外部回调实现
   * @param {*} fn 返回当前子模块 和 key,具体处理逻辑由调用方实现
   */
  forEachChild(fn) {
    Object.keys(this._children).forEach(key=>fn(this._children[key],key));
  }
}

export default Module;

组合state,通过path.slice(0, -1).reduce的方法,组装父子树

// 定义状态
        const state = store._modules.root.state; // 根状态
        installModule(store, state, [], store._modules.root);

function installModule(store, rootState, path, module) { // 递归安装
    let isRoot = !path.length; // 如果数组是空数组 说明是根,否则不是

    if (!isRoot) { // []
       /**
         * path为[aCount,cCount],state[key]为aCount的值{count:0}
         * path[path.length-1]为cCount,将cCount作为键,赋值给aCount,此时的module为cCount的值,module.state为cCount的值{count:0}
         */
        let parentState = path.slice(0, -1).reduce((state, key) => state[key], rootState);
        parentState[path[path.length-1]] = module.state
    }
        module.forEachChild((child, key) => { // aCount,bCount
        installModule(store, rootState, path.concat(key), child);
    })
}