怎么实现vuex源码的核心类ModuleCollection

简介: 怎么实现vuex源码的核心类ModuleCollection

怎么实现vuex源码的核心类ModuleCollection


本文想写 vuex 源码中核心的一个类,ModuleCollection,这个类是将参数格式化成一定的结构。

感觉这种模式,在别的源码里也常用,希望自己能真正理解并运用,所以特地有此篇。

vuex 使用的时候,传入类似这种参数

export default new Vuex.Store({
  modules: {
    aModule: { state: { msg: "a模块的颜酱" }, },
    bModule: {
      state: { msg: "b模块的颜酱" },
      modules: {
        cModule: { state: { msg: "c模块的颜酱" }, }
      }
    }
  },
  state: { msg: "hi, i am 颜酱" }
});

使用 c 模块的state的时候,是this.$store.state.b.c.msg

vuex为了方便使用参数,对参数,处理成如下格式,其实就是将父子关系更加明显化。

{
  "_raw": 根模块数据,
  "state": { "msg": "hi, i am 颜酱", },
  "_children": {
    "aModule": {
      "_raw":a模块的原来数据,
      "state": { "msg": "a模块的颜酱" },
      "_children": {},
    },
    "bModule": {
      "_raw": b模块的原来数据,
      "state": { "msg": "b模块的颜酱" },
      "_children": {
        "cModule": {
          "_raw": c模块的原来数据,
          "state": { "msg": "c模块的颜酱" },
          "_children": {},
        }
      }
    }
  }
}

每个模块都有的字段:_raw、state、children

我的思路:简单封装函数,返回正确的格式

开始,我写了个formatData方法,但不够优雅哇。

其实这个先试着自己写,处理完第一层逻辑,基本就ok,核心就是递归,只是递归的时候,注意children这个对象,这也算是这里的难点。

const options = {
  modules: {
    aModule: { state: { msg: "a模块的颜酱" } },
    bModule: {
      state: { msg: "b模块的颜酱" },
      modules: {
        cModule: { state: { msg: "c模块的颜酱" } }
      }
    }
  },
  state: { msg: "hi, i am 颜酱" }
};
const formatData = (options, res) => {
  res._raw = options;
  res.state = options.state;
  const childModules = options.modules;
  // 如果有模块的话
  if (childModules) {
    // 遍历模块
    Object.keys(childModules).forEach(childModuleName => {
      const curChildModule = childModules[childModuleName];
      // 这边没有的必须先设置{},这样才能赋值属性
      res.children = res.children ? res.children : {};
      res.children[childModuleName] = res.children[childModuleName]
        ? res.children[childModuleName]
        : {};
      // 以上两行代码都是为第二个参数准备的,防止undefined报错
      formatData(curChildModule, res.children[childModuleName]);
    });
  }
};
// 因为res是另外的,所以封装个高阶函数,这也使用起来很方便
const format = options => {
  let res = {};
  formatData(options, res);
  return res;
};
console.log(format(options));

vuex源码的思路:封装类,以路径的方式注册每个模块

源码很赞的是,以类的形式出现,核心逻辑是传入模块的路径和模块,来注册在相应的位置。

比如:根模块有b模块,b模块里有c模块,c模块里有d模块,那么d模块的路径就是[b,c,d]

注册d模块时候,其实这样就可以:

root.children.b.children.c.children.d = dModule

当然实际情况是,路径是动态的,需要我们写个函数去实现这样的功能:

为方便实现,将上面代码拆分下

// d的父模块
const parentModule = root.children.b.children.c
// 父模块里注册当前模块
parentModule.children.d = dModule

这样拆分的好处是,parentModule可以通过函数很方便的拿到

function register(path,module){
  const init = root
  const fn = (acc,cur) => acc[children][cur]
  // 父模块,这里想象成上面实际的例子
  const parentModule = path.slice(0,-1).reduce(fn,init)
  // 父模块里注册当前模块
  const curModuleName = path[path.length-1]
  parentModule.children[curModuleName] = module
}

以上就是最核心的逻辑了,当然这里的root还需要赋值的,其他的两个属性也需要加上,但这都不是事,不另做解释

const options = {
  modules: {
    aModule: { state: { msg: "a模块的颜酱" } },
    bModule: {
      state: { msg: "b模块的颜酱" },
      modules: {
        cModule: { state: { msg: "c模块的颜酱" } }
      }
    }
  },
  state: { msg: "hi, i am 颜酱" }
};
class ModuleCollection {
  constructor(options) {
    // 这里的root存放格式化之后的数据
    this.root = null;
    // 首次注册的是根模块,根模块的路径是空的
    this.register([], options);
  }
  register(path, module) {
    let moduleItem = {
      _raw: module,
      // 注意这里的children是对象
      children: {},
      state: module.state
    };
    // 第一次肯定是根模块,根模块必须赋值
    if (!this.root) {
      this.root = moduleItem;
    } else {
      // 核心代码:根据路径注册模块
      const curModuleName = path[path.length - 1];
      const fn = (acc, cur) => acc.children[cur];
      const parent = path.slice(0, -1).reduce(fn, this.root);
      parent.children[curModuleName] = moduleItem;
    }
    // 当前模块有子模块的话,继续挨个注册
    const childModules = module.modules;
    childModules &&
      Object.keys(childModules).forEach(childModuleName => {
        const curChildModule = childModules[childModuleName];
        // concat用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
        this.register(path.concat(childModuleName), curChildModule);
      });
  }
}
const modules = new ModuleCollection(options);
// 这就是格式化之后的数据了
console.log(modules.root);
console.log(modules.root.children.bModule.children);


目录
相关文章
|
3月前
|
JavaScript 前端开发 开发者
深入解析Angular装饰器:揭秘框架核心机制与应用——从基础用法到内部原理的全面教程
【8月更文挑战第31天】本文深入解析了Angular框架中的装饰器特性,包括其基本概念、使用方法及内部机制。装饰器作为TypeScript的关键特性,在Angular中用于定义组件、服务等。通过具体示例介绍了`@Component`和`@Injectable`装饰器的应用,展示了如何利用装饰器优化代码结构与依赖注入,帮助开发者构建高效、可维护的应用。
35 0
|
缓存 API 数据安全/隐私保护
vue3+ts项目搭建和封装(下篇)
vue3+ts项目搭建和封装(下篇)
|
6月前
|
传感器 JavaScript 前端开发
前端框架Svelte放弃TS,如何使用纯JS实现类型检查?
前端框架Svelte放弃TS,如何使用纯JS实现类型检查?
|
测试技术 调度
手撸vue3核心源码——响应式原理(scheduler以及onStop)
手撸vue3核心源码——响应式原理(scheduler以及onStop)
手撸vue3核心源码——响应式原理(scheduler以及onStop)
|
前端开发
前端学习案例4:ES6中的修饰器4
前端学习案例4:ES6中的修饰器4
61 0
前端学习案例4:ES6中的修饰器4
【vue2小知识】实现store中modules模块的封装与自动导入
store仓库中分模块时的需要每次导入index的问题
【vue2小知识】实现store中modules模块的封装与自动导入
|
前端开发
前端学习案例1:ES6中的修饰器1
前端学习案例1:ES6中的修饰器1
70 0
前端学习案例1:ES6中的修饰器1
|
前端开发
前端学习案例3:ES6中的修饰器3
前端学习案例3:ES6中的修饰器3
57 0
前端学习案例3:ES6中的修饰器3
|
前端开发
前端学习案例2:ES6中的修饰器2
前端学习案例2:ES6中的修饰器2
70 0
前端学习案例2:ES6中的修饰器2
|
JavaScript 前端开发