一,前言
上一篇,主要介绍了 Vuex 模块安装的实现,针对 action、mutation、getter 的收集与处理,主要涉及以下几个点:
- Vuex 模块安装的逻辑;
- Vuex 代码优化;
- Vuex 模块安装的实现;
- Vuex 初始化流程梳理;
本篇,继续介绍 Vuex 模块相关概念:Vuex 的 State 状态安装;
二,前文梳理
- 前面,通过依赖收集对 options 的格式化处理得到了“模块树”;
- 又通过依赖安装对“模块树”进行递归操作:从根模块开始,将对应的 getter、mutation、action 统一放入 Store 类的 this._actions、this._mutations、this._wrappedGetters 中;
Vuex 的初始化流程如下:
- 当项目引用并注册 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 实例已实现全局共享);
接下来,继续在 installModule 方法中处理“模块树”的 State 状态;
三,Vuex 模块安装-State状态安装
1,State 状态的安装逻辑
对“模块树”中状态的安装,就是将所有子模块上的 State 状态,挂载到对应父模块的 State 状态上;
- 处理范围:子模块,即
path.length > 0
时,执行状态安装逻辑; - 处理逻辑:将子模块的状态
module.state
,挂载到其父模块的状态上;
2,引出问题
所以,首先需要解决以下两个问题:
- 问题 1:如何找到当前子模块对应的父模块?
- 问题 2:如何将子模块状态挂载到父模块的状态上?
3,问题 1:如何找到当前子模块对应的父模块?
从“模块树”中,查找一个子模块对应的父模块,这个逻辑其实在模块收集时就已经写过了:
/**
* 安装模块
* @param {*} store 容器
* @param {*} rootState 根状态
* @param {*} path 所有路径
* @param {*} module 格式化后的模块对象
*/
const installModule = (store, rootState, path, module) => {
// 处理子模块:将子模块上的状态,添加到对应父模块的状态中;
if(path.length > 0){
// 从根状态开始逐层差找,找到当前子模块对应的父模块状态
let parent = path.slice(0, -1).reduce((memo, current)=>{
return memo[current]
}, rootState)
}
}
接下来,只需要向这个 parent
父模块的 State 状态中,添加当前子模块状态即可;
4,问题 2:如何将子模块状态挂载到父模块的状态上?
- 子模块状态:
module.state
; - 父模块状态:
parent
;
那么,直接向父模块状态中添加子模块状态就可以了吗?
parent[path[path.length-1]] = module.state;
右侧的进度条告诉我们不会这么简单的:
- 在 Vuex 中,模块是可以动态进行添加的;
- 在 Vuex 中,状态应该是响应式的;
因此,我们希望动态添加的模块也是响应式的数据;
如果,直接向对象中添加一个不存在的属性,是无法被声明为响应式数据的;
所以,需要通过 Vue.set
API 向父模块状态中添加子模块状态,以此实现对象新增属性为响应式数据;
这样,当 Vuex 动态注册模块时,新添加的状态属性就是响应式数据了;
备注:
- 如果使用
Vue.set
向一个非响应式对象添加属性,相当于直接为普通对象添加属性并赋值; - 此时,即 resetStoreVM 方法执行前,
parent
就是一个普通对象,当 resetStoreVM 方法执行完成后,才是响应式数据,因此,使用Vue.set
在两种状态下都是兼容的;
5,代码实现
从根模块开始递归处理,将当前子模块状态定义到其对应父模块状态上:
- 从根模块的状态开始找,返回当前模块所属的父模块 parent;
- 将当前模块的 State 状态设置到父模块 parent 的 path[path.length-1] 属性中;
即:将所有状态都设置到 rootState 上:
// src/vuex/store.js#installModule
const installModule = (store, rootState, path, module) => {
// 处理子模块:将子模块上的状态,添加到对应父模块的状态中;
if(path.length > 0){
// 从根状态开始逐层差找,找到当前子模块对应的父模块状态
let parent = path.slice(0, -1).reduce((memo, current)=>{
return memo[current]
}, rootState)
// 支持 Vuex 动态添加模块,将新增状态直接定义成为响应式数据;
Vue.set(parent, path[path.length-1], module.state);
}
}
6,执行情况分析
-
首次进入 installModule 方法,由于
path = []
,不会进入状态安装逻辑;此时,会遍历根模块中的actions
、mutations
、getters
分别放到store
实例中的_actions
、_mutations
、_wrappedGetters
中;最后,通过当前模块module.forEachChild
递归遍历子模块(深度优先递归),递归的终止条件是当父模块下不存在子模块时;备注:在遍历处理当前模块下的子模块时,完成 path 路径的拼接操作;
-
非首次进入 installModule 方法,此时
path.length > 0
, 进行子模块状态安装:先通过 path 找到当前子模块对应的父模块状态对象,并通过Vue.set
向其中添加子模块状态,该属性被直接定义为响应式数据;
打印状态安装完成后的state,此时 state 包含 Vuex 中全部模块的状态:
// src/vuex/store.js
export class Store {
constructor(options) {
const state = options.state;
// 收集所有模块中的action、mutation、getter 放到 Store 上
this._actions = {};
this._mutations = {};
this._wrappedGetters = {};
// 1,模块收集:options 格式化 -> Vuex 模块树
this._modules = new ModuleCollection(options);
console.log("格式化后的模块树对象", this._modules)
// 2,模块安装:
installModule(this, state, [], this._modules.root);
console.log("模块安装结果:state", state)
}
}
如上图所示:根模块状态中包含模块 A 和模块 B 的状态,模块 A 中包含模块 C 的状态;
四,结尾
本篇,主要介绍了 Vuex 的 State 状态安装,主要涉及以下几个点:
- State 状态的安装逻辑;
- 两个核心问题的思路;
- 代码实现以及执行情况分析;
下一篇,继续介绍 Vuex 模块相关概念:Vuex 数据响应式的实现
维护日志
- 20211008
- 全篇重构,调整文章目录结构,添加核心逻辑分析;