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)的過程中會調用vuexinstall方法,同時把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.將state放到一個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' } }
    }
})

源碼:https://gitee.com/aeipyuan/my_vuex

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章