当前位置: 首页 > news >正文

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

一,前言

上一篇,主要介绍了 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.jsimport { 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;// 遍历 mutationmodule.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);})})// 遍历 actionmodule.forEachAction((action, key) => {store._actions[key] = (store._actions[key] || []);store._actions[key].push((payload) => {action.call(store, store, payload);})})// 遍历 gettermodule.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:
    • 重新梳理全文:添加代码优化与流程梳理部分;
    • 添加必要的代码注释;
    • 添加测试截图;

相关文章:

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

一,前言 上一篇,主要介绍了 Vuex 模块收集的实现,主要涉及以下几个点: Vuex 模块的概念;Vuex 模块和命名空间的使用;Vuex 模块收集的实现-构建“模块树”; 本篇,继续介绍 Vuex 模…...

EOC第六章《块与中枢派发》

文章目录第37条:理解block这一概念第38条:为常用的块类型创建typedef第39条:用handler块降低代码分散程度第41条:多用派发队列,少用同步锁方案一:使用串行同步队列来将读写操作都安排到同一个队列里&#x…...

八、Git远程仓库操作——跨团队成员的协作

前言 前面一篇博文介绍了git团队成员之间的协作,现在在介绍下如果是跨团队成员的话,如何协作? 跨团队成员协作,其实就是你不属于那个项目的成员,你没有权限向那个仓库提交代码。但是github还有另一种 pull request&a…...

算法刷题打卡第88天:字母板上的路径

字母板上的路径 难度:中等 我们从一块字母板上的位置 (0, 0) 出发,该坐标对应的字符为 board[0][0]。 在本题里,字母板为board ["abcde", "fghij", "klmno", "pqrst", "uvwxy", "…...

UVa The Morning after Halloween 万圣节后的早晨 双向BFS

题目链接:The Morning after Halloween 题目描述: 给定一个二维矩阵,图中有障碍物和字母,你需要把小写字母移动到对应的大写字母位置,不同的小写字母可以同时移动(上下左右四个方向或者保持不动 &#xff0…...

Connext DDS属性配置参考大全(3)

Transport传输dds.participant.logging.time_based_logging.process_received_messagedds.participant.logging.time_based_logging.process_received_message.timeout...

Docker-安装Jenkins-使用jenkins发版Java项目

文章目录0.前言环境背景1.操作流程1.1前期准备工作1.1.1环境变量的配置1.2使用流水线的方式进行发版1.2.1新建流水线任务1.2.2流水线操作工具tools步骤stages步骤1:拉取代码编译步骤2:发送文件并启动0.前言 学海无涯,旅“途”漫漫,“途”中小记&#xff…...

spring 中的 Bean 是否线程安全

文章目录结论1、spring中的Bean从哪里来?2、spring中什么样的Bean存在线程安全问题?3、如何处理spring Bean的线程安全问题?结论 其实,Spring 中的 Bean 是否线程安全,其实跟 Spring 容器本身无关。Spring框架中没有提…...

微电网两阶段鲁棒优化经济调度方法[3]【升级优化版本】(Matlab代码实现)

💥💥💥💞💞💞欢迎来到本博客❤️❤️❤️💥💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑…...

C++入门教程||C++ 数据类型||C++ 变量类型

C 数据类型 使用编程语言进行编程时,需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着,当您创建一个变量时,就会在内存中保留一些空间。 您可能需要存储各种数据类型(比如字符型、宽字符型、整型…...

【visio使用技巧】图片导出pdf时去掉多余空白

问题 在visio导出pdf格式的图片时,往往会存在多余的白边,如下图所示: 解决方法 依次点击:菜单栏→文件→选项→自定义功能区→勾选“开发工具”→确定。 依次点击菜单栏→开发工具→显示ShapeSheet→页→Print Properties→将…...

Rust语言之Option枚举类型

概述 Option是Rust语言设计中最重要的枚举类型之一,它编码了其它语言中空值与非空值的概念,差异在于,Rust不会允许你像其它语言一样以非空值的方式来使用一个空值,这避免了很多错误。Option在标准库中的定义如下: pu…...

基于TimeQuest时序优化原理和方法

💡 回顾基于RTL逻辑时序优化的基本思路,在关键路径中插入寄存器来优化时序 分析最坏路径 通过前面对TimeQuest软件的理解,基本上可以找到关键路径,此文章主要对关键路径时序进行优化,使设计达到时序要求,以…...

LeetCode第332场周赛

2023.2.12LeetCode第332场周赛 6354. 找出数组的串联值 思路 双指针模拟&#xff0c;两个指针相遇的时候要特判 算法 class Solution { public:long long findTheArrayConcVal(vector<int>& nums) {long long ans 0;int i 0, j nums.size() - 1;while (i <…...

2023-2-12刷题情况

字母板上的路径 题目描述 我们从一块字母板上的位置 (0, 0) 出发&#xff0c;该坐标对应的字符为 board[0][0]。 在本题里&#xff0c;字母板为board [“abcde”, “fghij”, “klmno”, “pqrst”, “uvwxy”, “z”]&#xff0c;如下所示。 我们可以按下面的指令规则行动…...

拉普拉斯矩阵

拉普拉斯算子 Δff(xi1,yj)f(xi−1,yj)f(xi,yj1)f(xi,yj−1)−4f(xi,yj)∑(k,l)∈N(i,j)(f(xk,yl)−f(xi,yj))\begin{aligned} \Delta f & f\left(x_{i1}, y_j\right) f\left(x_{i-1},y_j\right) f\left(x_i,y_{j1}\right)f\left(x_i,y_{j-1}\right) - 4f\left(x_i,y_j\r…...

Top-1错误率、Top-5错误率等常见的模型算法评估指标解析

Top-1 错误率&#xff1a;指预测输出的概率最高的类别与人工标注的类别相符的准确率&#xff0c;就是你预测的label取最后概率向量里面最大的那一个作为预测结果&#xff0c;如过你的预测结果中概率最大的那个分类正确&#xff0c;则预测正确&#xff0c;否则预测错误。比如预测…...

Urho3D 容器类型

Urho3D实现了自己的字符串类型和模板容器&#xff0c;而不是使用STL。其基本原理如下&#xff1a; 在某些情况下提高了性能&#xff0c;例如使用PODVector类时。保证字符串和容器的二进制大小&#xff0c;以允许例如嵌入Variant对象内。减少了编译时间。直接命名和实现&#x…...

C语言学习笔记(四): 循环结构程序设计

while语句 定义 While语句是C语言中的循环语句&#xff0c;它按条件循环执行语句&#xff0c;直到条件不满足为止 语法格式如下: while(condition) {//循环体内容; }使用实例 求123…100 include <stdio.h> int main(){int i 1, sum 0;while (i<100){sum i …...

02 OpenCV图像通道处理

1 通道提取与合并 在数字图像处理中&#xff0c;图像通道是指一个图像中的颜色信息被分离为不同的颜色分量。常见的图像通道包括RGB通道、灰度通道、HSV通道等。 RGB通道是指将图像分离为红色、绿色和蓝色三个颜色通道&#xff0c;每个通道表示相应颜色的亮度。这种方式是最常…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

五年级数学知识边界总结思考-下册

目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解&#xff1a;由来、作用与意义**一、知识点核心内容****二、知识点的由来&#xff1a;从生活实践到数学抽象****三、知识的作用&#xff1a;解决实际问题的工具****四、学习的意义&#xff1a;培养核心素养…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

Go 并发编程基础:通道(Channel)的使用

在 Go 中&#xff0c;Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式&#xff0c;用于在多个 Goroutine 之间传递数据&#xff0c;从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...