Vuex简单实现
Vuex简单实现
1. 实现this.$store
全局访问
const install = (vue) => {
Vue = vue;
Vue.mixin({
beforeCreate() {
/* 获取根组件传递$store */
if (this.$options && this.$options.store) {
this.$store = this.$options.store;
} else {/* 从父组件传递 */
this.$store = this.$parent && this.$parent.$store;
}
}
})
}
export default { install, Store }//导出
在Vue.use(vuex)
的过程中会调用vuex
的install
方法,同时把Vue
作为形参传入,通过Vue.mixin
给所有的组件添加生命周期事件,先给根组件设置this.$store = this.$options.store
,然后让所有子组件继承
2.Store
类的实现
class Store {
constructor(options) {
/* 初始化 */
this.vm = new Vue({
data: { state: options.state }
})/* 使state里的数据响应式化 */
this.getters = {};
this.mutations = {};
this.actions = {};
/* 将modules转换格式 _raw _children state */
this.modules = new ModuleCollection(options);
console.log(this.modules);
/* 安装模块 */
installModules(this, this.state, [], this.modules.root);
}
get state() {/* 代理获取state */
return this.vm.state;
}
commit = (key, ...payload) => {
this.mutations[key].forEach(fn => fn(...payload));
}
dispatch = (key, ...payload) => {
this.actions[key].forEach(fn => fn(...payload));
}
registerModule(moduleName, module) {
/* 注册模块 */
this.modules.register([moduleName], module);/* (path,rootModule) */
/* 格式化后的模块 */
let rawModule = this.modules.root._children[moduleName];
installModules(this, this.state, [moduleName], rawModule);
}
}
1.将stat
e放到一个vue实例中,实现响应式,同时通过get state()
实现代理
2.初始化getters
,mutations
,actions
3.创建一个ModuleColeection
对象,将modules
转化为标准格式
class ModuleCollection {
constructor(options) {
this.register([], options)
}
/* path记录根到当前模块路径[b,c] */
register(path, rootModule) {
/* 创建当前模块的格式化对象 */
let rawModule = {
_raw: rootModule,
_children: {},
state: rootModule.state
}
/* 若还没有根,第一次进入,则给根模块赋值 */
if (!this.root) {
this.root = rawModule;
} else {
/* 找到当前模块父模块 [b,c] => this.root._children['b'] */
let parent = path.slice(0, -1).reduce((data, item) => {
return data._children[item];
}, this.root);
/* 示例:this.root._children['b']._children['c']=rawModule */
parent._children[path[path.length - 1]] = rawModule;
}
/* 遍历注册子模块 */
if (rootModule.modules) {
forEachValue(rootModule.modules, (moduleName, module) => {
this.register(path.concat(moduleName), module);
})
}
}
}
ModuleColeection
每次调用register
方法都会创建一个对象rawModule
,将每个模块的所有内容放到_raw
中,将state
数据放到state
中,用_children
来模块的直接子模块,第一次调用register
时将options
转化成的rawModule
赋给this.root
/* 创建当前模块的格式化对象 */
let rawModule = {
_raw: rootModule,
_children: {},
state: rootModule.state
}
利用封装的全局方法forEachValue
取得子模块的名字和内容,递归调用子模块进行注册
let forEachValue = (obj, callback) => {
Object.keys(obj).forEach(key => {
callback(key, obj[key]);
})
}
/* 遍历注册子模块 */
if (rootModule.modules) {
forEachValue(rootModule.modules, (moduleName, module) => {
this.register(path.concat(moduleName), module);
})
}
path.concat
的作用是记录路径,用于找到父模块,将自身放到父模块的_children
对象中,形成图中格式,例如下面代码:在不是根模块的情况下,register
传入的path=['b','c']
时,就可以推断c
模块属于第三层,通过前面的b
找到父模块,再将自己放到父模块的_children
对象。
/* 找到当前模块父模块 [b,c] => this.root._children['b'] */
let parent = path.slice(0, -1).reduce((data, item) => {
return data._children[item];
}, this.root);
/* 示例:this.root._children['b']._children['c']=rawModule */
parent._children[path[path.length - 1]] = rawModule;
4. installModules
安装模块
function installModules(store, rootState, path, rawModule) {
/* 把所有数据放到state上 */
if (path.length) {
/* 获取父模块 示例:['b','c'] => rootState['b'] */
let parent = path.slice(0, -1).reduce((data, item) => {
return data[item];
}, rootState);
/* rootState['b']['c'] = rawModule.state */
Vue.set(parent, path[path.length - 1], rawModule.state);
}
/* getters */
let getters = rawModule._raw.getters;
if (getters) {
forEachValue(getters, (key, value) => {
Object.defineProperty(store.getters, key, {
get: () => value(rawModule.state)
})
})
}
/* mutations */
let mutations = rawModule._raw.mutations;
if (mutations) {
forEachValue(mutations, (mutationName, value) => {
/* 收集所有模块的同名mutation */
let arr = store.mutations[mutationName] || (store.mutations[mutationName] = []);
arr.push((...payload) => { value(rawModule.state, ...payload) });
})
}
/* actions */
let actions = rawModule._raw.actions;
if (actions) {
forEachValue(actions, (actionName, value) => {
let arr = store.actions[actionName] || (store.actions[actionName] = []);
arr.push((...payload) => { value(store, ...payload) });
})
}
/* 遍历子模块 */
forEachValue(rawModule._children, (name, value) => {
installModules(store, rootState, path.concat(name), value);
})
}
实现在组件中类似{{$store.state.b.c.num}}
的调用方式,原理就是将所有的数据根据嵌套关系放到state
中,利用reduce
寻找父模块,调用Vue.set
添加响应式数据
/* 把所有数据放到state上 */
if (path.length) {
/* 获取父模块 示例:['b','c'] => rootState['b'] */
let parent = path.slice(0, -1).reduce((data, item) => {
return data[item];
}, rootState);
/* rootState['b']['c'] = rawModule.state */
Vue.set(parent, path[path.length - 1], rawModule.state);
}
对所有模块的getters
进行劫持,直接使用$store.getters
访问,限制就是模块之间命名不能重复
/* getters */
let getters = rawModule._raw.getters;
if (getters) {
forEachValue(getters, (key, value) => {
Object.defineProperty(store.getters, key, {
get: () => value(rawModule.state)//执行函数返回结果
})
})
}
将所有模块的mutations
成员放到store.mutations
对象上去,然后根据名称划分为多个订阅数组,commit
调用时就可以直接触发所有模块执行同名的函数,actions
区别在于传回的第一个参数时store
,这样做的原因是实现actions
到达事件后可以调用mutations
成员执行操作
/* ------------installMutations------------ */
/* mutations */
let mutations = rawModule._raw.mutations;
if (mutations) {
forEachValue(mutations, (mutationName, value) => {
/* 收集所有模块的同名mutation */
let arr = store.mutations[mutationName] || (store.mutations[mutationName] = []);
arr.push((...payload) => { value(rawModule.state, ...payload) });
})
}
/* actions */
let actions = rawModule._raw.actions;
if (actions) {
forEachValue(actions, (actionName, value) => {
let arr = store.actions[actionName] || (store.actions[actionName] = []);
arr.push((...payload) => { value(store, ...payload) });
})
}
/* -------------------Store------------------- */
commit = (key, ...payload) => {
this.mutations[key].forEach(fn => fn(...payload));
}
dispatch = (key, ...payload) => {
this.actions[key].forEach(fn => fn(...payload));
}
递归安装子模块
/* 遍历子模块 */
forEachValue(rawModule._children, (name, value) => {
installModules(store, rootState, path.concat(name), value);
})
5.registerModule
方法拓展模块
/* ------------Store方法------------ */
registerModule(moduleName, module) {
/* 注册模块 */
this.modules.register([moduleName], module);/* (path,rootModule) */
/* 格式化后的模块 */
let rawModule = this.modules.root._children[moduleName];
installModules(this, this.state, [moduleName], rawModule);
}
/* -----------外部调用方式----------- */
/* 增加组件 */
store.registerModule('d', {
state: { num: 'd1' },
modules: {
e: { state: { num: 'e1' } }
}
})