一、modules使用背景:
在使用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: {},
modules: {
district: {
state: { state_district: "海淀" },
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: {
district: {
_rawModule: {
state: { state_district: "海淀" },
getters: {},
mutations: {},
actions: {}
},
_children: {},
state: { state_district: "海淀" }
}
},
state: { state_city: "北京" }
}
},
state: { state_country: "中国" }
}
}
module类的实现:
在源码中每个模块的生成通过一个module类来转换。
module的代码如下:
class Module {
constructor(rawModule) {
this._children = Object.create(null);
this._rawModule = rawModule;
this.state = rawModule.state;
}
}
_children:用于存放子模块。_rawModule:存储自己模块的伪(未被加工)模块时的内容。state:存储自己模块的数据内容。
当前_children属性是空对象,如何向该对象中添加子模块数据?
Module类是对单个模块的处理,而vuex中支持多模块,如果当前模块有子模块的话需要放到_children中,这时候需要通过ModuleCollection类来处理。
ModuleCollection类的实现:
class ModuleCollection {
constructor(options) {
this.register([], options);
}
register(path, rawModule) {
// 将传入的`options`通过`Module`类生成新的模块
let newModule = new Module(rawModule);
if (!this.root) {
this.root = newModule;
} else {
// 将生成的新模块添加到它的父元素上
// 第三次调用时,path = [ 'city' , 'district' ]
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 => {
// 第一次调用res === 'city',第二次调用res === 'district'
this.register(path.concat(res), rawModule.modules[res]);
});
}
}
}
ModuleCollection核心的功能是register函数,将所有的子模块整合成树状结构。
register做了什么?
- 将传入的
options通过Module类生成新的模块。 - 将生成的新模块添加到它的父元素上。
- 如果当前的模块有子元素,重新执行
register,也就是重复第1,2步骤。
register函数的path和rawModule两个参数的作用?
rawModule:表示当前未加工的模块。path:表示当前模块和所有父级模块的模块名的集合。
为什么要判断是否是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;
核心思想:将当前模块添加到上级模块的_children里。
三、什么是模块安装?
模块的安装最主要的功能就是将注册完的数据里每个模块的state,getters,mutation,action递归安装到Store类上,从而用户可以在组件中可以通过this.$store.xxx来操作数据。
先来看下平时vuex怎么调用这些模块:
//获取state
this.$store.state.state_country
//获取getters
this.$store.getters.getter_country
//设置mutations
this.$store.commit("mutation_country","中国")
//设置actions
this.$store.dispatch("action_country",{})
看下Store类具体做了什么?
class Store {
constructor(options) {
// 通过实例化`Vue`对象将`state`数据作为`Vue`的属性实现双向绑定
this.vm = new Vue({
data() {
return {
temp_data: options.state
};
}
});
// 将options传入的数据进行格式化,即数据注册
this.modules = new ModuleCollection(options);
// 新增了`getters`,`mutations`和`actions`三个属性
this.getters = {};
this.mutations = {};
this.actions = {};
// 通过`installModule`函数将各子模块的`getters`,`mutations`和`actions`整合到`Store`对应的属性上
installModule(this, this.state, [], this.modules.root);
}
get state() {
return this.vm.temp_data;
}
commit = (mutationName, payload) => {
this.mutations[mutationName].forEach(fn => fn(mutationName, payload));
};
dispatch = (actionName, payload) => {
this.actions[actionName].forEach(fn => fn(actionName, payload));
};
}
installModule函数的实现:
installModule函数主要是安装state,getters,mutations和actions,如果有子模块的话,重复之前的安装步骤,以下是代码的实现:
//将格式化后的数据递归绑定到getters/mutations/actions
const installModule = (store, rootState, path, modules) => {
//分别安装state,getters,mutations和actions
installState(path, rootState, modules);
installGetters(store, modules);
installMutations(store, modules);
installActions(store, modules);
//如果有子模块的话
if (Object.keys(modules._children).length > 0) {
Object.keys(modules._children).forEach(childModuleName => {
installModule(
store,
rootState,
path.concat(childModuleName),
modules._children[childModuleName]
);
});
}
};
store:表示当前Store类。rootState:表示当前要安装模块的state模块。path:表示当前模块和所有父级模块的模块名的集合。modules:表示当前要安装的模块。
installState实现过程
const installState = (path, rootState, modules) => {
// 如果当前模块不是父级模块
if (path.length > 0) {
// 获取到当前模块的父级模块的state对象
let parentState = path.slice(0, -1).reduce((root, current) => {
return root[current];
}, rootState);
// 给当前模块的父级模块的state对象中增加当前模块的state属性
Vue.set(parentState, path[path.length - 1], modules.state);
}
};
最终store上的state参考如下结构:
{
state_country: "中国",
city: {
state_city: "北京",
district: {
state_district: "海淀"
}
}
};
installGetters实现过程
const installGetters = (store, modules) => {
const getters = modules._rawModule.getters;
if (getters) {
// 遍历当前模块的getters
Object.keys(getters).forEach(getterName => {
// 将`modules`的`getters`属性名遍历绑定到`store`的`getters`上
Object.defineProperty(store.getters, getterName, {
get: () => {
return getters[getterName](store.state);
}
});
});
}
};
installGetters就是将modules的getters绑定到store上的getters属性上。通过Object.defineProperty将modules的getters属性名遍历绑定到store的getters上,在组件中获取store.getters上的属性实际上获取的是modules上的属性,相当于通过Object.defineProperty做了数据劫持。
installMutations实现过程
const installMutations = (store, modules) => {
const mutations = modules._rawModule.mutations;
if (mutations) {
// 遍历当前模块的mutations
Object.keys(mutations).forEach(mutationName => {
// 将`modules`的`mutations`绑定到`store`上的`mutations`对象上
let temp = store.mutations[mutationName] || (store.mutations[mutationName] = []);
temp.push((payload) => {
mutations[mutationName](modules.state, payload);
});
});
}
};
installMutations就是将modules的mutations绑定到store上的mutations对象上。store的mutations是对象,键名是mutations的函数名,键值是数组容器,存放所有同名的mutation函数,mutation是不怕重名的,重名的话将逐个执行。mutation函数需要两个参数,一个是mutations的state,另一个是用户传入的参数。
最终store上的mutations参考如下结构:
{
mutation_city: [
(state, val) => (state.state_city = "北京"),
(state, val) => (state.state_city = val)
],
mutation_country: [
(state, val) => (state.state_country = val)
]
};