本文想写 vuex 源码中核心的一个类,ModuleCollection
,这个类是将参数格式化成一定的结构。
感觉这种模式,在别的源码里也常用,希望自己能真正理解并运用,所以特地有此篇。
vuex 使用的时候,传入类似这种参数
export default new Vuex.Store({
modules: {
aModule: { state: { msg: "a模块的颜酱" }, },
bModule: {
state: { msg: "b模块的颜酱" },
modules: {
cModule: { state: { msg: "c模块的颜酱" }, }
}
}
},
state: { msg: "hi, i am 颜酱" }
});
使用 c 模块的state
的时候,是this.$store.state.b.c.msg
。
vuex
为了方便使用参数,对参数,处理成如下格式,其实就是将父子关系更加明显化。
{
"_raw": 根模块数据,
"state": { "msg": "hi, i am 颜酱", },
"_children": {
"aModule": {
"_raw":a模块的原来数据,
"state": { "msg": "a模块的颜酱" },
"_children": {},
},
"bModule": {
"_raw": b模块的原来数据,
"state": { "msg": "b模块的颜酱" },
"_children": {
"cModule": {
"_raw": c模块的原来数据,
"state": { "msg": "c模块的颜酱" },
"_children": {},
}
}
}
}
}
每个模块都有的字段:_raw、state、children
我的思路:简单封装函数,返回正确的格式
开始,我写了个formatData
方法,但不够优雅哇。
其实这个先试着自己写,处理完第一层逻辑,基本就ok,核心就是递归,只是递归的时候,注意children
这个对象,这也算是这里的难点。
const options = {
modules: {
aModule: { state: { msg: "a模块的颜酱" } },
bModule: {
state: { msg: "b模块的颜酱" },
modules: {
cModule: { state: { msg: "c模块的颜酱" } }
}
}
},
state: { msg: "hi, i am 颜酱" }
};
const formatData = (options, res) => {
res._raw = options;
res.state = options.state;
const childModules = options.modules;
// 如果有模块的话
if (childModules) {
// 遍历模块
Object.keys(childModules).forEach(childModuleName => {
const curChildModule = childModules[childModuleName];
// 这边没有的必须先设置{},这样才能赋值属性
res.children = res.children ? res.children : {};
res.children[childModuleName] = res.children[childModuleName]
? res.children[childModuleName]
: {};
// 以上两行代码都是为第二个参数准备的,防止undefined报错
formatData(curChildModule, res.children[childModuleName]);
});
}
};
// 因为res是另外的,所以封装个高阶函数,这也使用起来很方便
const format = options => {
let res = {};
formatData(options, res);
return res;
};
console.log(format(options));
vuex源码的思路:封装类,以路径的方式注册每个模块
源码很赞的是,以类的形式出现,核心逻辑是传入模块的路径和模块,来注册在相应的位置。
比如:根模块有b模块,b模块里有c模块,c模块里有d模块,那么d模块的路径就是[b,c,d]
注册d模块时候,其实这样就可以:
root.children.b.children.c.children.d = dModule
当然实际情况是,路径是动态的,需要我们写个函数去实现这样的功能:
为方便实现,将上面代码拆分下
// d的父模块
const parentModule = root.children.b.children.c
// 父模块里注册当前模块
parentModule.children.d = dModule
这样拆分的好处是,parentModule
可以通过函数很方便的拿到
function register(path,module){
const init = root
const fn = (acc,cur) => acc[children][cur]
// 父模块,这里想象成上面实际的例子
const parentModule = path.slice(0,-1).reduce(fn,init)
// 父模块里注册当前模块
const curModuleName = path[path.length-1]
parentModule.children[curModuleName] = module
}
以上就是最核心的逻辑了,当然这里的root
还需要赋值的,其他的两个属性也需要加上,但这都不是事,不另做解释
const options = {
modules: {
aModule: { state: { msg: "a模块的颜酱" } },
bModule: {
state: { msg: "b模块的颜酱" },
modules: {
cModule: { state: { msg: "c模块的颜酱" } }
}
}
},
state: { msg: "hi, i am 颜酱" }
};
class ModuleCollection {
constructor(options) {
// 这里的root存放格式化之后的数据
this.root = null;
// 首次注册的是根模块,根模块的路径是空的
this.register([], options);
}
register(path, module) {
let moduleItem = {
_raw: module,
// 注意这里的children是对象
children: {},
state: module.state
};
// 第一次肯定是根模块,根模块必须赋值
if (!this.root) {
this.root = moduleItem;
} else {
// 核心代码:根据路径注册模块
const curModuleName = path[path.length - 1];
const fn = (acc, cur) => acc.children[cur];
const parent = path.slice(0, -1).reduce(fn, this.root);
parent.children[curModuleName] = moduleItem;
}
// 当前模块有子模块的话,继续挨个注册
const childModules = module.modules;
childModules &&
Object.keys(childModules).forEach(childModuleName => {
const curChildModule = childModules[childModuleName];
// concat用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
this.register(path.concat(childModuleName), curChildModule);
});
}
}
const modules = new ModuleCollection(options);
// 这就是格式化之后的数据了
console.log(modules.root);
console.log(modules.root.children.bModule.children);
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!