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

news/2024/4/29 11:18:23/文章来源:https://blog.csdn.net/ABAP_Brave/article/details/128996045

一,前言

上一篇,主要介绍了 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:
    • 重新梳理全文:添加代码优化与流程梳理部分;
    • 添加必要的代码注释;
    • 添加测试截图;

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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

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

利用git reflog 命令来查看历史提交记录,并使用提交记录恢复已经被删除掉的分支

一.问题描述 当我们在操作中手误删除了某个分支,那该分支中提交的内容也没有了,我们可以利用git reflog这个命令来查看历史提交的记录从而恢复被删除的分支和提交的内容 二.模拟问题 1.创建git仓库,并提交一个文件 [rootcentos7-temp /da…

TrueNas篇-trueNas Scale安装

安装TrueNAS Scale 在尝试trueNas core时发下可以成功安装,但是一直无法成功启动,而且国内对我遇见的错误几乎没有案例,所以舍弃掉了,而且trueNas core是基于Linux的,对Linux的生态好了很多,还可以可以在t…

最强大的人工智能chatGPT不会还有人没用过吧,再不用就out了

🔗 运行环境:chatGPT 🚩 撰写作者:左手の明天 🥇 精选专栏:《python》 🔥 推荐专栏:《算法研究》 #### 防伪水印——左手の明天 #### 💗 大家好🤗&#x1f9…

Win11下Linux子系统迁移方法及报错解决

Win11 将Linux子系统从C盘迁移到其他盘Win11下Linux子系统迁移方法及报错解决1、下载LxRunOffline2、ERROR:directory is not empty 报错解决参考链接Win11下Linux子系统迁移方法及报错解决 C盘满了,Ubuntu子系统占了100多G怎么办?直接将子系…

一文讲清chatGPT的发展历程、能力来源和复现它的关键之处

1. ChatGPT是什么 chatGPT是什么?这可能是最近被问的最多的一个。 大家第一反应这应该是GPT系列的一个最新模型,普通大众可能更愿意把它看做是一个人工智能。实际上,它其实就是一个基于大规模语言模型的对话系统产品。官网对它定义十分的明…

【三维点云】01-激光雷达原理与应用

文章目录内容概要1 激光雷达原理1.1 什么是激光雷达?1.2 激光雷达原理1.3 激光雷达分类三角法TOF法脉冲间隔测量法幅度调制的相位测量法相干法激光雷达用途2 激光雷达安装、标定与同步2.1 激光雷达安装方式考虑因素2.2 激光雷达点云用途2.3 数据融合多激光雷达数据融…

【蓝桥杯单片机】Keil5中怎么添加STC头文件;从烧录软件中添加显示添加成功后新建工程时依旧找不到

蓝桥杯单片机的芯片型号:IAP15F2K61S2 添加头文件:STC15F2K60S2.H 【1】如何通过烧录软件添加STC头文件: 从ATC-ISP的Keil仿真设置中添加(同时自动下载仿真驱动)仔细阅读添加说明 KEIL5添加STC芯片库_Initdev的博客-…

【寒假day4】leetcode刷题

🌈一、选择题❤1.下列哪一个是析构函数的特征( )。A: 析构函数定义只能在类体内 B: 一个类中只能定义一个析构函数 C: 析构函数名与类名相同 D: 析构函数可以有一个或多个参数答案:B答案解析:析构函数是构造函…

C语言(文件输入输出操作)

目录 一.文件 1.文件概念 2.文本模式和二进制模式 (1)模式结尾映射 (2)存储精度 3.I/O级别 一.文件 1.文件概念 文件:在磁盘或固态硬盘上一段已命名的存储区。对于C来说,文件就是一系列连续的字节,每个字节都能被单独读取(在计算机当…

.Net Core对于`RabbitMQ`封装分布式事件总线

首先我们需要了解到分布式事件总线是什么;分布式事件总线是一种在分布式系统中提供事件通知、订阅和发布机制的技术。它允许多个组件或微服务之间的协作和通信,而无需直接耦合或了解彼此的实现细节。通过事件总线,组件或微服务可以通过发布或…

大家心心念念的RocketMQ5.x入门手册来喽

1、前言 为了更好的拥抱云原生,RocketMQ5.x架构进行了大的重构,提出了存储与计算分离的设计架构,架构设计图如下所示: RocketMQ5.x提供了一套非常建议的消息发送、消费API,并统一放在Apache顶级开源项目rocketmq-clie…

TC3xx FlexRay™ 协议控制器 (E-Ray)-01

1 FlexRay™ 协议控制器 (E-Ray) E-Ray IP 模块根据为汽车应用开发的 FlexRay™ 协议规范 v2.1 执行通信【performs communication according to the FlexRay™ 1) protocol specification v2.1】。使用最大指定时钟,比特率可以编程为高达 10 Mbit/s 的值。连接到物…

就现在!为元宇宙和Web3对互联网的改造做准备!

欢迎来到Hubbleverse 🌍 关注我们 关注宇宙新鲜事 📌 预计阅读时长:8分钟 本文仅代表作者个人观点,不代表平台意见,不构成投资建议。 如今,互联网是各种不同的网站、应用程序和平台的集合。由于彼此分离…

STM32单片机GSM短信自动存取快递柜

实践制作DIY- GC0104-自动存取快递柜 一、功能说明: 基于STM32单片机设计-自动存取快递柜 二、功能介绍: STM32F103C系列最小系统板0.96寸OLED显示器DY-SV17F串口语音播报模块4*4矩阵键盘GSM短信模块4路舵机(模拟4个柜子) ***…

【openGauss实战9】深度分析分区表

📢📢📢📣📣📣 哈喽!大家好,我是【IT邦德】,江湖人称jeames007,10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】!😜&am…

Syzkaller学习笔记---更新syz-extract/syz-sysgen(一)

Syzkaller学习笔记Syzkaller 安装文件系统内核Android common kernel参考文献syzkaller 源码阅读笔记-1前言syz-extractmainarchListcreateArchesworkerprocessArchprocessFileextractcheckUnsupportedCallsarchList小结syz-sysgenmainprocessJob()generateExecutorSyscalls()w…

2016-ICLR-Order Matters- Sequence to sequence for sets

2016-ICLR-Order Matters- Sequence to sequence for sets Paper: [https://arxiv.org/pdf/1511.06391.pdf](https://arxiv.org/pdf/1511.06391.pdf) Code: 顺序重要性:集合的顺序到序列 摘要 许多需要从观察序列映射或映射到观察序列的复杂任务现在可以使用序列…

C++创建多线程的方法总结

下个迭代有个任务很有趣,用大量的线程去访问一个接口,直至其崩溃为止,这就需要多线程的知识,这也不是什么难事,总结一下C中的多线程方法:std、boost、pthread、windows api。 目录 一、多线程预备知识 二…

基于SpringBoot实现ChatGPT-QQ机器人

概述 近期ChatGPT火爆全球,在其官方网站上也列举了非常全面的应用案例,仅仅上线两个月活跃用户数已经达到1亿,成为历史上用户数增长最快的面向消费者的应用 快速体验 OpenAI官网对外提供了标准的 API 接口,可以通过HTTP请求进行…