【手写 Vuex 源码】第七篇 - Vuex 的模块安装

news2024/12/28 8:39:11

一,前言

上一篇,主要介绍了 Vuex 模块收集的实现,主要涉及以下几个点:

  • Vuex 模块的概念;
  • Vuex 模块和命名空间的使用;
  • Vuex 模块收集的实现-构建“模块树”;

本篇,继续介绍 Vuex 模块相关概念:Vuex 模块安装的实现;


二,前文梳理

Vuex 的根模块,即 index 模块:src/store/index.js:

  • 根模块通过 modules 注册子模块:示例包含 A、B 两个子模块;
  • 模块A 又包含了子模块 C,这样就构建了一个三层的树形结构;
  • 所以,Vuex 的模块,理论上是一棵支持无限层级的模块树;

依赖收集的过程:就是根据 Vuex 模块关系进行数据格式化,体现到代码上就是递归;

  • 通过 ModuleCollection 类,递归地对 Vuex 模块进行格式化处理,以便于后续的状态操作;

这里,大家可以借鉴组合模式,用于处理树型结构,如组织架构等层级嵌套的场景;

  • 通过 register(path, rootModule) 进行模块注册:
  • path 数组类型,当前待注册模块的完整路径;
  • rootModule 当前待注册模块对象;

至此,在 Vuex 中就完成了模块间层级关系的维护,从而递归构建出一棵“模块树”对象;

备注:

  • 同名模块会在 Vuex 的模块收集阶段被覆盖;
  • 多个模块中存在同名状态时,默认将同时触发更新 $store.commit('changeNum', 5);可添加 namespaced 命名空间进行隔离;
  • 添加了 namespaced 命名空间后,状态操作需添加命名空间标识,如 $store.commit('moduleA/changeNum',5)

下一步,根据格式化后的“模块树”对象,实现 Vuex 的模块安装;


三,模块安装的逻辑

模块收集:将模块对象格式化成为一棵“模块树”;

模块安装:递归“模块树”并将所有模块的 getter、mutation、action 定义到当前 store 实例中;

  • 从根模块开始进行模块安装,递归处理格式化后的“模块树”对象;
  • 根据模块名称,将全部子模块定义到根模块上,同时将状态合并到根模块上;

在 Store 类中,创建 installModule 模块安装方法:对当前模块对象进行递归处理;

从根模块开始,将对应的 getter、mutation、action 统一放入 Store 类中的 this._actions、this._mutations、this._wrappedGetters;

备注:由于模块对象不便于能力的扩展,考虑重构为类,将模块相关操作进行封装提供外部调用;


四,代码优化

优化1:将模块对象重构为模块类

创建 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;

修改 ModuleCollection 类,将模块对象更新为 Module 类:

import Module from "./module";

class ModuleCollection {
  constructor(options) {
    this.register([], options);
  }
  
  register(path, rootModule) {
    
    // 格式化:构建 Module 对象
    // 通过类的方式产生实例,便于后续的扩展
    let newModule = new Module(rootModule);
    // let newModule = {
    //   _raw: rootModule,        // 当前模块的完整对象
    //   _children: {},           // 当前模块的子模块
    //   state: rootModule.state  // 当前模块的状态
    // } 

    if (path.length == 0) {
      this.root = newModule;
    } else {
      let parent = path.slice(0, -1).reduce((memo, current) => {
        // 此时 memo 为 Module 类,使用 getChild 方法进行处理;
        return memo.getChild(current);
        // return memo._children[current];
      }, this.root)
			 // 此时 memo 为 Module 类,使用 addChild 方法进行处理;
      parent.addChild(path[path.length - 1], newModule);
      // parent._children[path[path.length - 1]] = newModule
    }

    if (rootModule.modules) {
      Object.keys(rootModule.modules).forEach(moduleName => {
        let module = rootModule.modules[moduleName];
        this.register(path.concat(moduleName), module)
      });
    }
  }
}

export default ModuleCollection;

优化2:抽取对象遍历工具方法

代码中多次使用 Object.keys 进行对象遍历操作,可封装为工具函数;

创建 src/vuex/utils.js 文件,统一存放 vuex 插件使用的工具函数:

// src/vuex/utils.js

/**
 * 对象遍历,返回 value、key,具体处理由外部实现
 * @param {*} obj       需要遍历的对象
 * @param {*} callback  对当前索引的处理,又外部实现
 */
export const forEachValue = (obj, callback) =>{
  Object.keys(obj).forEach(key=>callback(obj[key],key));
}

使用工具函数替换 Object.keys:

// src/vuex/module/module-collection.js

import { forEachValue } from "../utils";
import Module from "./module";

class ModuleCollection {
  constructor(options) {
    this.register([], options);
  }
  register(path, rootModule) {
    let newModule = new Module(rootModule);
    if (path.length == 0) {
      this.root = newModule;
    } else {
      let parent = path.slice(0, -1).reduce((memo, current) => {
        return memo.getChild(current);
      }, this.root)
      parent.addChild(path[path.length - 1], newModule);
    }

    if (rootModule.modules) {
      forEachValue(rootModule.modules,(module,moduleName)=>{
        this.register(path.concat(moduleName),module)
      })
      // Object.keys(rootModule.modules).forEach(moduleName => {
      //   let module = rootModule.modules[moduleName];
      //   this.register(path.concat(moduleName), module)
      // });
    }
  }
}

export default ModuleCollection;
import { forEachValue } from "../utils";

class Module {
  constructor(newModule) {
    this._raw = newModule;
    this._children = {};
    this.state = newModule.state
  }
  getChild(key) {
    return this._children[key];
  }
  addChild(key, module) {
    this._children[key] = module
  }
  forEachMutation(fn) {
    if (this._raw.mutations) {
      forEachValue(this._raw.mutations, fn)
      // Object.keys(this._raw.mutations).forEach(key=>fn(this._raw.mutations[key],key));
    }
  }
  forEachAction(fn) {
    if (this._raw.actions) {
      forEachValue(this._raw.actions, fn);
      // Object.keys(this._raw.actions).forEach(key=>fn(this._raw.actions[key],key));
    }
  }
  forEachGetter(fn) {
    if (this._raw.getters) {
      forEachValue(this._raw.getters, fn);
      // Object.keys(this._raw.getters).forEach(key=>fn(this._raw.getters[key],key));
    }
  }
  forEachChild(fn) {
    forEachValue(this._children, fn);
    // Object.keys(this._children).forEach(key=>fn(this._children[key],key));
  }
}

export default Module;

优化后测试

image.png

功能正常,模块对象已重构为 Module 类,添加了对当前模块 getters、mutations、actions 的遍历处理;


五,模块安装的实现

在 src/vuex/store.js 中,创建 installModule 方法:用于 Vuex 的模块安装操作;

// src/vuex/store.js

/**
 * 安装模块
 * @param {*} store       容器
 * @param {*} rootState   根状态
 * @param {*} path        所有路径
 * @param {*} module      格式化后的模块对象
 */
const installModule = (store, rootState, path, module) => {

  // 遍历当前模块中的 actions、mutations、getters 
  // 将它们分别定义到 store 中的 _actions、_mutations、_wrappedGetters;

  // 遍历 mutation
  module.forEachMutation((mutation, key) => {
    // 处理成为数组类型:每个 key 可能会存在多个需要被处理的函数
    store._mutations[key] = (store._mutations[key] || []);
    // 向 _mutations 对应 key 的数组中,放入对应的处理函数
    store._mutations[key].push((payload) => {
      // 执行 mutation,传入当前模块的 state 状态
      mutation.call(store, module.state, payload);
    })
  })
  // 遍历 action
  module.forEachAction((action, key) => {
    store._actions[key] = (store._actions[key] || []);
    store._actions[key].push((payload) => {
      action.call(store, store, payload);
    })
  })
  // 遍历 getter
  module.forEachGetter((getter, key) => {
    // 注意:getter 重名将会被覆盖
    store._wrappedGetters[key] = function () {
      // 执行对应的 getter 方法,传入当前模块的 state 状态,返回执行结果
      return getter(module.state)   
    }
  })
  // 遍历当前模块的儿子
  module.forEachChild((child, key) => {
    // 递归安装/加载子模块
    installModule(store, rootState, path.concat(key), child);
  })
}
依靠 Module 类提供的模块处理方法,深度递归地将全部模块中的 action、mutation、getter 统一收集到了 store 实例中对应的 _actions、_mutations、_wrappedGetters 中;

模块安装结果测试:

// src/vuex/store.js

// 容器的初始化
export class Store {
  constructor(options) {
    const state = options.state;
    
    this._actions = {};
    this._mutations = {};
    this._wrappedGetters = {};

    this._modules = new ModuleCollection(options);

    installModule(this, state, [], this._modules.root);
    console.log("模块安装结果:_mutations", this._mutations)
    console.log("模块安装结果:_actions", this._actions)
    console.log("模块安装结果:_wrappedGetters", this._wrappedGetters)
  }
	// ...
}

打印 _actions、_mutations、_wrappedGetters 结果:

_mutations 共 4 个:根模块、模块 A、模块 B、模块 C;

_actions 共 1 个:根模块;

_wrappedGetters 共 1 个:根模块;

六,流程梳理

  • 当项目引用并注册 vuex 插件时,即 Vuex.use(vuex),将执行 Vuex 插件中的 install 方法;
  • install 方法,接收外部传入的 Vue 实例,并通过 Vue.mixin 实现 store 实例的全局共享;
  • 项目中通过 new Vuex.Store(options) 配置 vuex 并完成 store 状态实例的初始化;
  • 在 Store 实例化阶段时,将会对 options 选项进行处理,此时完成 Vuex 模块收集和安装操作;
  • new Vue 初始化时,将 store 实例注入到 vue 根实例中(此时的 store 实例已实现全局共享);

七,结尾

本篇,主要介绍了 Vuex 模块安装的实现,完成了 action、mutation、getter 的收集和处理,主要涉及以下几个点:

  • Vuex 模块安装的逻辑;
  • Vuex 代码优化;
  • Vuex 模块安装的实现;
  • Vuex 初始化流程梳理;

下一篇,继续介绍 Vuex 模块相关概念:Vuex 状态的处理;


维护日志

  • 20211006:
    • 重新梳理全文:添加代码优化与流程梳理部分;
    • 添加必要的代码注释;
    • 添加测试截图;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/340265.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gradle命令

环境搭建 $ mkdir /opt/gradle $ unzip -d /opt/gradle gradle-7.6-bin.zip $ ls /opt/gradle/gradle-7.6 LICENSE NOTICE bin getting-started.html init.d lib media配置环境变量 $ export PATH=$PATH:/opt/gradle/gradle-7.6/bin检查配置是否ok gradle -v Android …

Elasticsearch7.8.0版本进阶——分布式集群(应对故障)

目录一、Elasticsearch集群的安装1.1、Elasticsearch集群的安装(win10环境)1.2、Elasticsearch集群的安装(linux环境)二、应对故障(win10环境集群演示)2.1、启动集群(三个节点)2.2、…

Lecture4 反向传播(Back Propagation)

目录 1 问题背景 1.1计算图(Computational Graph) 1.2 激活函数(Activation Function)引入 1.3 问题引入 2 反向传播(Back Propagation) 2.1 为什么要使用反向传播 2.2 前馈运算(Forward Propagation…

Allegro如何更改临时高亮的颜色设置操作指导

Allegro如何更改临时高亮的颜色设置操作指导 在用Allegro做PCB设计的时候,当移动或者高亮某个对象之前,会被临时高亮一个颜色,方便查看,类似下图 运行高亮命令的时候,器件被临时高亮成了白色 软件默认的是白色,如何更改成其它颜色? 具体操作如下 点击Display选择Color…

西瓜书读书笔记—绪论

文章目录机器学习典型的机器学习过程基本术语归纳偏好机器学习 机器学习:致力于研究如果通过计算的手段,利用经验来改善系统自身的性能 在计算机系统中,“经验” 通常以 “数据” 形式存在,因此,机器学习所研究的主要内…

《计算机组成与设计》05. 大而快:层次化存储

文章目录局部性原理存储层次结构存储层次结构示意图传输数据示意图Cache 基础映射方式直接映射全相连映射组相连映射Cache 访问直接映射例题 —— Cache 容量计算组相联映射处理写操作3C 模型Cache 失效问题 —— 通过更改 Cache 块容量,以此通过空间局部性来降低失…

【Linux常用指令合集】

基本的增删改查 ls:显示文件或目录-l:列出文件详细信息l(list)-a:列出当前目录下所有文件及目录,包括隐藏的a(all) mkdir 目录名:创建目录-p:级联创建 cd 目录:切换目录 pwd:显示当…

SpringMVC--视图、RESTful案例、处理AJAX请求

SpringMVC的视图 SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户 SpringMVC视图的种类很多,默认有转发视图和重定向视图 当工程引入jstl的依赖,转发视图会自动转换为JstlView 若使用的视图技术为…

GitHub个人资料自述与管理主题设置

目录 关于您的个人资料自述文件 先决条件 添加个人资料自述文件 删除个人资料自述文件 管理主题设置 补充:建立一个空白文件夹 关于您的个人资料自述文件 可以通过创建个人资料 README,在 GitHub.com 上与社区分享有关你自己的信息。 GitHub 在个…

【触摸屏功能测试】MQTT_STD本地调试说明-测试记录

1、MQTT简介 MQTT是一种基于发布/订阅模式的“轻量级”通讯协议。它是针对受限的、低带宽的、高延迟的、网络不可靠的环境下的网络通讯设备设计的。 发布是指客户端将消息传递给服务器,订阅是指客户端接收服务器推送的消息。每个消息有一个主题,包含若干…

七大设计原则之迪米特法则应用

目录1 迪米特法则介绍2 迪米特法则应用1 迪米特法则介绍 迪米特原则(Law of Demeter LoD)是指一个对象应该对其他对象保持最少的了解,又叫最少知 道原则(Least Knowledge Principle,LKP),尽量降低类与类之…

30分钟吃掉wandb可视化自动调参

wandb.sweep: 低代码,可视化,分布式 自动调参工具。使用wandb 的 sweep 进行超参调优,具有以下优点。(1)低代码:只需配置一个sweep.yaml配置文件,或者定义一个配置dict,几乎不用编写调参相关代码。(2)可视化…

Django框架之视图和URL

视图和URL 站点管理页面做好了, 接下来就要做公共访问的页面了.对于Django的设计框架MVT. 用户在URL中请求的是视图.视图接收请求后进行处理.并将处理的结果返回给请求者.使用视图时需要进行两步操作 1.定义视图2.配置URLconf 1. 定义视图 视图就是一个Python函数&#xff0c…

没有她的通讯录(C语言实现)

🚀write in front🚀 📝个人主页:认真写博客的夏目浅石. 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝 📣系列专栏:夏目的C语言宝藏 💬总结:希望你看完之…

优劣解距离法TOPSIS——清风老师

TOPSIS法是一种常用的综合评价方法,能充分利用原始数据的信息,其结果能精确地反映各评价方案之间的差距。 基本过程为先将原始数据矩阵统一指标类型(一般正向化处理)得到正向化的矩阵,再对正向化的矩阵进行标准化处理…

​ICLR 2023 | GReTo:以同异配关系重新审视动态时空图聚合

©PaperWeekly 原创 作者 | 周正阳单位 | 中国科学技术大学论文简介动态时空图数据结构在多种不同的学科中均普遍存在,如交通流、空气质量观测、社交网络等,这些观测往往会随着时间而变化,进而引发节点间关联的动态时变特性。本文的主要…

springboot学习(八十) springboot中使用Log4j2记录分布式链路日志

在分布式环境中一般统一收集日志,但是在并发大时不好定位问题,大量的日志导致无法找出日志的链路关系。 可以为每一个请求分配一个traceId,记录日志时,记录此traceId,从网关开始,依次将traceId记录到请求头…

【C#】[带格式的字符串] 复合格式设置字符串与使用 $ 的字符串内插 | 如何格式化输出字符串

复合格式输出 string name "Fred"; String.Format("Name {0}, hours {1:hh}", name, DateTime.Now);通过指定相同的参数说明符,多个格式项可以引用对象列表中的同一个元素。 例如,通过指定“0x{0:X} {0:E} {0:N}”等复合格式字符…

凸优化学习:PART3凸优化问题(持续更新)

凸优化问题 凸优化问题的广义定义: 目标函数为凸函数约束集合为凸集 一、优化问题 基本用语 一般优化问题的描述: minimize⁡f0(x)subject to fi(x)⩽0,i1,⋯,mhi(x)0,i1,⋯,p(1)\begin{array}{ll} \operatorname{minimize} & f_0(x) \\ \text { s…

Centos7 安装Hadoop3 单机版本(伪分布式版本)

环境版本CentOS-7JDK-8Hadoop-3CentOS-7 服务器设置设置静态IP查看IP配置在/etc/sysconfig/network-scripts/目录下的ifcfg-ens33文件中。[rootHadoop3-master sbin]# cd /etc/sysconfig/network-scripts [rootHadoop3-master network-scripts]# ll 总用量 232 -rw-r--r--. 1 r…