本文参考珠峰架构公开课之
vuex实现原理
在使用vuex过程中,如果数据量很大可以用vuex的modules实现模块化。modules实现过程个人认为可以分为数据注册和模块的安装,本篇主要将的是数据注册。
什么是数据注册?
数据注册是将用户传入的options做了一次数据的格式化,转换成模块树状结构,实现父子模块的整合。比如用户传入的options如下:
export default new Vuex.Store({
state: { state_country: "中国" },
getters: {},
mutations: {},
actions: {},
modules: {
city: {
state: { state_city: "上海" },
getters: {},
mutations: {},
actions: {}
}
}
});
转换好的数据结构如下:
{
root: {
_rawModule: {
state: { state_country: "中国" },
getters: {},
mutations: {},
actions: {},
modules: {
city: {
state: { state_city: "上海" },
getters: {},
mutations: {},
actions: {}
}
}
},
_children: {
city: {
_rawModule: {
state: { state_city: "上海" },
getters: {},
mutations: {},
actions: {}
},
_children: {},
state: { state_city: "上海" }
}
},
state: { state_country: "中国" }
}
}
可以发现,options传入的数据被处理成了一个新的数据结构。源码中每个模块的生成通过一个module类来转换。
Module
module的代码如下:
class Module {
constructor(rawModule) {
this._children = Object.create(null);
this._rawModule = rawModule;
this.state = rawModule.state;
}
}
在
vue全家桶的源码中,如果命名的变量名是raw开头的话代表是未经过加工的数据。
传入options单个模块的未经过加工的数据,返回一个包含_rawModule,_children,state三个属性的数据结构:
-
_children:用于存放子模块。 -
_rawModule:存储自己模块的伪(未被加工)模块时的内容。 -
state:存储自己模块的数据内容。
Module类是对单个模块的处理,而vuex中支持多模块,如果当前模块有子模块的话需要放到_children中,这时候需要通过ModuleCollection类来处理。
ModuleCollection
先来看下源码:
class ModuleCollection {
constructor(options) {
this.register([], options);
}
register(path, rawModule) {
let newModule = new Module(rawModule);
if (!this.root) {
this.root = newModule;
} else {
let parentModule = path.slice(0, -1).reduce((root, moduleName) => {
return root._children[moduleName];
}, this.root);
parentModule._children[path[path.length - 1]] = newModule;
}
//如果子元素还有modules,需要递归注册
if (rawModule.modules) {
Object.keys(rawModule.modules).forEach(res => {
this.register(path.concat(res), rawModule.modules[res]);
});
}
}
}
ModuleCollection核心的功能是register函数,将所有的子模块整合成树状结构。
register做了什么?
- 将传入的
options通过Module类生成新的模块。 - 将生成的新模块添加到它的父元素上。
- 如果当前的模块有子元素,重新执行
register,也就是重复第1,2步骤。
register函数的path和rawModule两个参数的作用?
-
rawModule:表示当前未加工的模块。 -
path:表示当前模块和所有父级模块的模块名的集合。在递归过程中,如果有子模块的话会将当前模块的模块名合并子模块的模块名做为下一个register函数中的path参数。举个例子:如果根模块有子模块city,city子模块又有district子模块,因为有三级,按层级划分就是根模块,city模块和district模块,那这个path在注册每个模块时处理结果如下:
第一次对根模块执行register,path为[]。
第二次对city模块执行register,path为[ 'city' ]。
第三次对district模块执行register,path为[ 'city' , 'district' ]。
为何要判断是否是this.root?
通过实例化ModuleCollection返回的数据结构,它的根模块数据是包在root对象里的。因为根模块调用register,也就是第一次调用register函数的时候是没有this.root的,类里面没有这个属性,所以一定返回false,于是就将当前模块赋值给this.root,因为this.root已被赋值,所以不会再进入这个判断。
除了以上问题,还有个比较难理解的是下面这段代码:
let parentModule = path.slice(0, -1).reduce((root, moduleName) => {
return root._children[moduleName];
}, this.root);
parentModule._children[path[path.length - 1]] = newModule;
path.slice(0, -1)表示父级路径的集合,然后通过reduce获取当前模块的上级模块,并将当前模块添加到上级模块的_children里。
文章的最后
讲完了数据注册,接下来还有模块的安装,模块的安装最主要的功能就是将注册完的数据里每个模块的state,getters,mutation,action递归安装到Store类上,从而用户可以在组件中可以通过this.$store.xxx来操作数据。