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' } }
}
})