vuex原理解析:手寫一個vuex

1. 什麼是vuex?

vuex是專門爲vuejs應用設計的一套狀態管理模式,提供了一套集中管理數據的概念和用法,用來解決中大型項目中組件間大量數據共享的問題。它的核心概念包括state、mutations、actions,基於這些它的工做流程是: vue

image

2. vuex源碼實現流程

vuex最終export了一個對象這個對象包括了一個install方法和一個類Store, 注意對應咱們的使用方法vuex

let store = new Vuex.Store({})
複製代碼

install方法供Vue.use()使用,內部使用Vue.mixin在每個組件的beforeCreate生命週期中給組件混入了一個$store屬性,這個屬性就是那個惟一的store。編程

從new Vuex.Store(options)開始,有如下主要流程:api

  1. 拿到用戶傳入的options,進行格式化,核心是register方法
  2. 拿到格式化後的數據,從根模塊開始遞歸的進行安裝,核心是installModules方法

除了以上內容以外vuex還導出了一些輔助函數好比mapState、mapMutations、mapActions。數組

3. mutation爲何只能是同步?

嚴格模式下,mutation只能是同步。非嚴格模式下,若是你硬要使用異步也能夠,固然不建議這麼作。瀏覽器

若是咱們使用異步的方法來更改狀態,設想一下,咱們但願使用log來記錄狀態更改來源,當兩個組件同時修改同一個狀態,對應有兩個log, 那麼這兩個log到底分別對應的是哪一個組件呢?bash

由於是異步的因此這兩個修改動做的前後順序沒法保證,那麼咱們根本沒法判斷log和組件的對應關係,可能你覺得是這個組件的log,實際上它是另外一個組件的,這就讓咱們沒辦法去精確的進行調試和跟蹤狀態更改信息。異步

4. vuex中的state是怎麼響應化的?

很簡單就是直接使用 new Vue() 構造了一個Vue實例:async

this.vm = new Vue({
    data: {
        state: options.state// 響應化處理
    }
});
複製代碼

這樣state就是響應式的了,看到這其實你就應該明白爲何Vuex只能用在Vue項目中了。函數

當咱們在訪問state時還須要一層代理:

get state() {// 獲取實例上的state屬性就會執行此方法
    return this.vm.state
}
複製代碼

固然這還沒結束,以上只是針對最外層的state,那麼若是咱們寫了modules,modules內部的模塊的state是怎麼處理的呢?

從這開始咱們就接觸到了Vuex最核心的東西了,上面咱們說了new一個Store實例時會先將用戶傳入的數據進行格式化,這就是register方法主要作的事,下面咱們就看看他究竟是怎麼格式化?格式化最終的結果是什麼?

咱們舉一個例子:

let store = new Vuex.Store({
    state: {// 單一數據源
        age: 10
    },
    getters: {
        myAge(state) {
            return state.age + 20;
        }
    },
    strict: true,
    // 嚴格模式下只能使用同步
    mutations: {
        syncChange(state, payload) {
            state.age += payload;
        }
    },
    actions: {
        asyncChange({commit}, payload) {
            setTimeout(() => {
                commit('syncChange', payload);
            }, 1000);
        }
    },
    modules: {
        a: {
            namespaced: true,
            state: {
                age: 'a100'
            }, 
            mutations: {
                syncChange() {
                    console.log('a-syncChange');
                }
            }
        },
        b: {
            namespaced: true,
            state: {
                age: 'b100'
            },
            mutations: {
                syncChange() {
                    console.log('b-syncChange');
                }
            },
            modules: {
                c: {
                    namespaced: true,
                    state: {
                        age: 'c100'
                    },
                    mutations: {
                        syncChange() {
                            console.log('c-syncChange');
                        }
                    },
                }
            }
        }
    }
});
複製代碼

先看看最終的結果

{
    _raw: rootModule,
    state: rootModule.state,
    _children: {
        a: {
            _raw: aModule,
            state: aModule.state,
            _children: {}
        },
        b: {
            _raw: aModule,
            state: aModule.state,
            _children: {
                c: {
                    _raw: cModule,
                    state: cModule.state,
                    _children: {}
                }
            }
        }
    }
} 
複製代碼

能夠看到,這仍是一個樹形結構, _raw就是用戶本身寫的格式化以前的模塊,state單獨拿了出來是由於咱們在安裝模塊時會用到,_children放的就是modules下面的內容, 固然子模塊下還有可能有孫子模塊...

劃重點,register

register(path, rootModule) {    
    let rawModule = {
        _raw: rootModule,// 用戶傳入的模塊定義
        _children: {},
        state: rootModule.state
    }

    rootModule.rawModule = rawModule;// 雙向記錄

    if (!this.root) {
        this.root = rawModule;
    } else {
        // 經典方法 找到當前模塊的父親
        let parentModule = path.slice(0, -1).reduce((root, current) => { // 註冊c的時候 [b, c].slice(0, -1) 至關於 [b, c].slice(0, 1) 的結果就是[b]
            return root._children[current]
        }, this.root);
        parentModule._children[path[path.length-1]] = rawModule;
    }
    if (rootModule.modules) {
        forEach(rootModule.modules, (moduleName, module) => {
            // 註冊a, [a] a模塊的定義
            // 註冊b, [b] b模塊的定義

            // 註冊c, [b, c] c模塊的定義
            this.register(path.concat(moduleName), module);
        });
    }
}
複製代碼

register有兩個參數,第一個是路徑,數組類型,這裏提一下,Vuex中判斷模塊層級關係就是使用數組,這也是一個經典通用的作法,下面會細說。第二個是根模塊也就是進行格式化的起點,這裏起點就是用戶傳入的數據。

往下咱們看到有這一句

rootModule.rawModule = rawModule;// 雙向記錄
複製代碼

這個實際上是爲了動態註冊時用的,以後會說。

再往下就是經典的找爸爸了:

對於根模塊,先定義一個根root,做爲起點,接下來的子模塊會先走forEach,使用path.concat(moduleName)肯定模塊層級關係,而後進行遞歸註冊,這裏的forEach方法是咱們本身封裝的:

let forEach = (obj, callback) => {
    Object.keys(obj).forEach(key => {
        callback(key, obj[key]);
    })
}
複製代碼

注意slice的用法,slice(0, -1)就是取出除了當前模塊以外的模塊,不清楚slice用法的趕忙去補補吧~

找到當前模塊的父模塊以後就把當前模塊放在父模塊的_children中,這樣一次父子模塊的註冊就算完成了。

好了,咱們回到正題,上面說了最外層的state已經實現了響應式,那麼modules內部的state如何實現響應式處理?

這就又涉及到了Vuex的另外一個核心方法installModules方法了:

function installModule(store, rootState, path, rawModule) {// 安裝時用的rawModule是格式化後的數據
    // 安裝子模塊的狀態
    // 根據當前用戶傳入的配置 判斷是否添加前綴
    let root = store.modules.root // 獲取到最終整個的格式化結果
    let namespace = path.reduce((str, current) => {
        root= root._children[current];// a
        str = str + (root._raw.namespaced ? current + '/' : '');
        return str;
    }, '');
    // console.log(path, namespace);
    if(path.length > 0) {// 代表是子模塊
        // 若是是c,就先找到b
        // [b,c,e] => [b, c] => c 
        let parentState = path.slice(0, -1).reduce((rootState, current) => {
            return rootState[current];
        }, rootState);

        // vue的響應式不能對不存在的屬性進行響應化
        Vue.set(parentState, path[path.length-1], rawModule.state);
    }
    // 安裝getters
    let getters = rawModule._raw.getters;
    if (getters) {
        forEach(getters, (getterName, value) => {
            Object.defineProperty(store.getters, namespace + getterName, {
                get: () => {
                    // return value(rawModule.state);// rawModule就是當前的模塊
                    return value(getState(store, path));
                }
            });
        });
    }
    // 安裝mutation
    let mutations = rawModule._raw.mutations;
    if (mutations) {
        forEach(mutations, (mutationName, value) => {
            let arr = store.mutations[namespace + mutationName] || (store. mutations[namespace + mutationName] = []);
            arr.push((payload) => {
                // value(rawModule.state, payload);// 真正執行mutation的地方
                value(getState(store, path), payload);
                store.subs.forEach(fn => fn({type: namespace + mutationName, payload: payload}, store.state)); // 這就用到了切片
            });
        });
    }
    // 安裝action
    let actions = rawModule._raw.actions;
    if (actions) {
        forEach(actions, (actionName, value) => {
            let arr = store.actions[namespace + actionName] || (store.actions[namespace + actionName] = []);
            arr.push((payload) => {
                value(store, payload);
            });
        });
    }
    // 處理子模塊
    forEach(rawModule._children, (moduleName, rawModule) => {
        installModule(store, rootState, path.concat(moduleName), rawModule)
    });
}
複製代碼

installModules方法有四個參數:

  1. store: new Vuex.Store()獲得的實例
  2. rootState: 最外層state
  3. path: 模塊層級關係
  4. rawModule: 某個格式化後的模塊

咱們如今只關注這一段代碼:

if(path.length > 0) {// 代表是子模塊
    // 若是是c,就先找到b
    // [b,c,e] => [b, c] => c 
    let parentState = path.slice(0, -1).reduce((rootState, current) => {
        return rootState[current];
    }, rootState);

    // vue的響應式不能對不存在的屬性進行響應化
    Vue.set(parentState, path[path.length-1], rawModule.state);
}
複製代碼

看到了吧,仍是找爸爸,以前是找父模塊,此次是找父模塊的狀態。而後使用Vue.set()方法,對modules下的模塊進行響應化處理。以後依舊是遞歸

// 處理子模塊
forEach(rawModule._children, (moduleName, rawModule) => {
    installModule(store, rootState, path.concat(moduleName), rawModule)
});
複製代碼

5. getters實現原理

基於以上, 咱們直接看installModules這一段代碼:

// 安裝getters
let getters = rawModule._raw.getters;
if (getters) {
    forEach(getters, (getterName, value) => {
        Object.defineProperty(store.getters, namespace + getterName, {
            get: () => {
                // return value(rawModule.state);// rawModule就是當前的模塊
                return value(getState(store, path));
            }
        });
    });
}
複製代碼

這裏面涉及了兩個新東西:

  1. namespace
  2. getState

先說namespace,看下面這段代碼

// 根據當前用戶傳入的配置 判斷是否添加前綴
let root = store.modules.root // 獲取到最終整個的格式化結果
let namespace = path.reduce((str, current) => {
    root= root._children[current];// a
    str = str + (root._raw.namespaced ? current + '/' : '');
    return str;
}, '');
複製代碼

仍是reduce,加上了命名空間以後原來的方法xxx就變成了a/b/xxx。

getState方法:

// 遞歸的獲取每個模塊的最新狀態
function getState(store, path) {   
    let local = path.reduce((newState, current) => {
        return newState[current];
    }, store.state);
    return local;
}
複製代碼

仍是reduce,getState主要是結合Vuex實現數據持久化使用,下面咱們會介紹,這裏先跳過。

ok, 大體瞭解了這兩個東西以後,咱們再看getters, 能夠看出最終全部的getter都使用Object.defineProperty定義在了store.getters這個對象中,注意這裏至關於把原本的樹形結構給鋪平了, 這也就是當咱們不適用namespace時,必定要保證不能重名的緣由。

6. mutations實現原理

// 安裝mutation
let mutations = rawModule._raw.mutations;
if (mutations) {
    forEach(mutations, (mutationName, value) => {
        let arr = store.mutations[namespace + mutationName] || (store. mutations[namespace + mutationName] = []);
        arr.push((payload) => {
            value(getState(store, path), payload);
        });
    });
}
複製代碼

知道了getters的原理mutations的原理就也知道了,這裏就是訂閱,對應的commit就是發佈。

7. actions實現原理

// 安裝action
let actions = rawModule._raw.actions;
if (actions) {
    forEach(actions, (actionName, value) => {
        let arr = store.actions[namespace + actionName] || (store.actions[namespace + actionName] = []);
        arr.push((payload) => {
            value(store, payload);
        });
    });
}
複製代碼

同理,依舊是訂閱和發佈。

8. 動態註冊module如何實現?

動態註冊是Vuex提供的一個類方法registerModule

// 動態註冊
registerModule(moduleName, module) {
    if (!Array.isArray(moduleName)) {
        moduleName = [moduleName];
    }
    this.modules.register(moduleName, module); // 這裏只作了格式化
    installModule(this, this.state, moduleName, module.rawModule);// 從當前模塊開始安裝
}
複製代碼

它有兩個參數,第一個是要註冊的模塊名稱,這個參數對應的register方法中的path,應該是一個數組類型。第二個是對應的選項。內部仍是先格式化而後安裝的流程,安裝時就用到了上面提到的雙向綁定

rootModule.rawModule = rawModule;
複製代碼

對應的

rawModule._raw = rootModule;
複製代碼

rootModule是格式化以前的模塊,rawModule是格式化以後的模塊。rootModule

注意在安裝的時候是從當前的模塊開始的,並非從根模塊開始。

使用方式:

註冊一個單一模塊

store.registerModule('d', {
    state: {
        age: 'd100'
    }
});
複製代碼

註冊一個有層級的模塊

store.registerModule(['b','c','e'], {
    state: {
        age: 'e100'
    }
});
複製代碼

9. 命名空間

多模塊下,若是不使用命名空間爲何不可以重名?

這個上面說getters實現原理時就提到過,由於安裝時,不管是getters仍是mutations、actions, 要麼是在一個對象中鋪平,要麼是在一個數組中鋪平,若是重名且不使用命名空間勢必會衝突。

10. vuex插件

用戶選項中除了state、getters、mutations、actions等以外還有一個plugins選項,爲每個mutation暴露一個鉤子,結合Vuex提供的subscribe方法就可以監聽到每一次的mutation信息。

插件其實就是函數,當new Vuex.Store() 時就會去執行一次

this.subs = [];
let plugins = options.plugins;
plugins.forEach(plugin => plugin(this));
複製代碼

內部其實仍是發佈和訂閱的應用,這裏咱們實現兩個經常使用插件

  1. logger
  2. 狀態持久化

在此以前咱們先看下Vuex提供的subscribe方法, 這是一個類方法

subscribe(fn) {
    this.subs.push(fn);
}
複製代碼

能夠看到就是訂閱,既然是用來監聽mutation變化,那發佈的位置必然是和mutation相關的,接下來咱們更改下mutation的安裝

// 安裝mutation
let mutations = rawModule._raw.mutations;
if (mutations) {
    forEach(mutations, (mutationName, value) => {
        let arr = store.mutations[namespace + mutationName] || (store. mutations[namespace + mutationName] = []);
        arr.push((payload) => {
            value(getState(store, path), payload);
            store.subs.forEach(fn => fn({type: namespace + mutationName, payload: payload}, store.state)); // 這就用到了切片
        });
    });
}
複製代碼

當執行了mutation以後,再去執行subs裏面的每個方法,這裏就是發佈了。在這裏咱們也看到了另一個編程的亮點:切片,

arr.push((payload) => {
    value(getState(store, path), payload);
    store.subs.forEach(fn => fn({type: namespace + mutationName, payload: payload}, store.state)); // 這就用到了切片
});
複製代碼

這裏若是咱們不考慮subscribe,徹底就能夠寫成

arr.push(value);
複製代碼

由於value就是一個mutation,是一個方法,直接存起來就行了,可是這樣一來就無法作其餘事情了,而使用切片就很方便咱們擴展,這就是切片編程的魅力所在。

下面咱們就看看logger插件的實現

function logger(store) {
    let prevState = JSON.stringify(store.state);// 默認狀態, 須要用JSON.stringify作一層深拷貝,不然preState是引用類型,那麼當store.state變化,preState立馬就跟着變化,這樣就沒法打印出上一次的狀態
    // let prevState = store.state;// 默認狀態
    // 訂閱
    store.subscribe((mutation, newState) => {// 每次調用mutation 此方法就會執行
        console.log(prevState);
        console.log(mutation);
        console.log(JSON.stringify(newState));
        prevState = JSON.stringify(newState);// 保存最新狀態
    });
}
複製代碼

數據持久化

function persists(store) {
    let local = localStorage.getItem('VUEX:state');
    if (local) {
        store.replaceState(JSON.parse(local));// 會用local替換全部狀態
    }
    store.subscribe((mutation, state) => {
        // 屢次更改只記錄一次, 須要作一個防抖
        debounce(() => {
            localStorage.setItem('VUEX:state', JSON.stringify(state));
        }, 500)();
    });
}
複製代碼

原理就是使用了瀏覽器自帶的一個api localStorage,這裏有一個新方法replaceState, 這也是一個類方法

replaceState(newState) {
    this.vm.state = newState;
}
複製代碼

該方法用來更新狀態, 這裏須要注意,咱們更改的僅僅是狀態state, 而getters、 mutations、 actions執行時仍舊使用的是舊的狀態,這個是在安裝時決定的,所以咱們還須要讓他們在每次執行的時候可以拿到最新的狀態,因此還記得上面說的getState方法嗎?

這裏作了一個小的優化就是用節流作了一個優化, 以應對接二連三的更改狀態,至於節流就再也不這贅述了。

最後咱們說下插件的使用:

plugins: [
    persists,
    logger 
],
複製代碼

注意logger方法, 第一次狀態改變時,prev state只包含非動態註冊的模塊, next state包含全部模塊,這是由於第一次執行logger方法的時候傳入的store尚未包含動態註冊的模塊。

11. 輔助函數實現原理

Vuex提供了mapState、mapGetters、mapMutations、mapActions這幾個輔助方法,方便咱們書寫。咱們把這幾個分紅兩類:

  1. mapState、mapGetters 這兩個咱們使用時是放在computed中的
computed: {
    ...mapState(['age']),// 解構處理
    ...mapGetters(['myAge'])
    /* age() { return this.$store.state.age;// 和mapState效果同樣 } */
},
複製代碼
  1. mapMutations、mapActions這兩個使用時是放在methods中的
methods: {
    // ...mapMutations(['syncChange']),
    ...mapMutations({aaa: 'syncChange'}),// 使用別名
    ...mapActions({bbb: 'asyncChange'})
}
複製代碼

mapState

export function mapState (stateArr) {// {age: fn}
    let obj = {};
    stateArr.forEach(stateName => {
        obj[stateName] = function() {
            return this.$store.state[stateName];
        }
    });
    return obj;
}
複製代碼

mapGetters

export function mapGetters(gettersArr) {
    let obj = {};
    gettersArr.forEach(getterName => {
        obj[getterName] = function() {
            return this.$store.getters[getterName];
        }
    });
    return obj;
}
複製代碼

mapMutations

export function mapMutations(obj) {
    let res = {};
    Object.entries(obj).forEach(([key, value]) => {
        res[key] = function(...args) {
            this.$store.commit(value, ...args);
        }
    });
    return res;
}
複製代碼

mapActions

export function mapActions(obj) {
    let res = {};
    Object.entries(obj).forEach(([key, value]) => {
        res[key] = function(...args) {
            this.$store.dispatch(value, ...args);
        }
    });
    return res;
}
複製代碼

咱們這裏分別實現了傳入數組和對象類型的參數,源碼中使用normalizeMap方法兼容了兩者。最終返回了一個對象,所以咱們使用時須要進行解構。

完整代碼

let Vue;

let forEach = (obj, callback) => {
    Object.keys(obj).forEach(key => {
        callback(key, obj[key]);
    })
}

class ModuleCollection {
    constructor(options) {
        // 深度遍歷,將全部的子模塊都遍歷一遍
        this.register([], options);
    }
    register(path, rootModule) {    
        let rawModule = {
            _raw: rootModule,// 用戶傳入的模塊定義
            _children: {},
            state: rootModule.state
        }

        rootModule.rawModule = rawModule;// 雙向記錄

        if (!this.root) {
            this.root = rawModule;
        } else {
            // 經典方法 找到當前模塊的父親
            let parentModule = path.slice(0, -1).reduce((root, current) => { // 註冊c的時候 [b, c].slice(0, -1) 至關於 [b, c].slice(0, 1) 的結果就是[b]
                return root._children[current]
            }, this.root);
            parentModule._children[path[path.length-1]] = rawModule;
        }
        if (rootModule.modules) {
            forEach(rootModule.modules, (moduleName, module) => {
                // 註冊a, [a] a模塊的定義
                // 註冊b, [b] b模塊的定義

                // 註冊c, [b, c] c模塊的定義
                this.register(path.concat(moduleName), module);
            });
        }
    }
}

// 遞歸的獲取每個模塊的最新狀態
function getState(store, path) {   
    let local = path.reduce((newState, current) => {
        return newState[current];
    }, store.state);
    return local;
}
function installModule(store, rootState, path, rawModule) {// 安裝時用的rawModule是格式化後的數據
    // 安裝子模塊的狀態
    // 根據當前用戶傳入的配置 判斷是否添加前綴
    let root = store.modules.root // 獲取到最終整個的格式化結果
    let namespace = path.reduce((str, current)  => {
        root= root._children[current];// a
        str = str + (root._raw.namespaced ? current + '/' : '');
        return str;
    }, '');
    // console.log(path, namespace);
    if(path.length > 0) {// 代表是子模塊
        // 若是是c,就先找到b
        // [b,c,e] => [b, c] => c 
        let parentState = path.slice(0, -1).reduce((rootState, current) => {
            return rootState[current];
        }, rootState);

        // vue的響應式不能對不存在的屬性進行響應化
        Vue.set(parentState, path[path.length-1], rawModule.state);
    }
    // 安裝getters
    let getters = rawModule._raw.getters;
    if (getters) {
        forEach(getters, (getterName, value) => {
            Object.defineProperty(store.getters, namespace + getterName, {
                get: () => {
                    // return value(rawModule.state);// rawModule就是當前的模塊
                    return value(getState(store, path));
                }
            });
        });
    }
    // 安裝mutation
    let mutations = rawModule._raw.mutations;
    if (mutations) {
        forEach(mutations, (mutationName, value) => {
            let arr = store.mutations[namespace + mutationName] || (store. mutations[namespace + mutationName] = []);
            arr.push((payload) => {
                // value(rawModule.state, payload);// 真正執行mutation的地方
                value(getState(store, path), payload);
                store.subs.forEach(fn => fn({type: namespace + mutationName, payload: payload}, store.state)); // 這就用到了切片
            });
        });
    }
    // 安裝action
    let actions = rawModule._raw.actions;
    if (actions) {
        forEach(actions, (actionName, value) => {
            let arr = store.actions[namespace + actionName] || (store.actions[namespace + actionName] = []);
            arr.push((payload) => {
                value(store, payload);
            });
        });
    }
    // 處理子模塊
    forEach(rawModule._children, (moduleName, rawModule) => {
        installModule(store, rootState, path.concat(moduleName), rawModule)
    });
}

class Store {
    constructor(options) {
        // console.log(options);
        // 獲取用戶傳入的全部屬性
        // this.state = options.state;
        this.vm = new Vue({
            data: {
                state: options.state// 響應化處理
            }
        });
       

        this.getters = {};// store內部使用的getters
        this.mutations = {};
        this.actions = {};

        // 1. 須要將用戶傳入的對象格式化操做
        this.modules = new ModuleCollection(options);
        // 2. 遞歸安裝模塊 ,從根模塊開始
        installModule(this, this.state, [], this.modules.root);
        this.subs = [];
        let plugins = options.plugins;
        plugins.forEach(plugin => plugin(this));
    }
    subscribe(fn) {
        this.subs.push(fn);// 能夠屢次訂閱
    }
    replaceState(newState) {
        this.vm.state = newState;// 更新狀態, 這裏須要注意,咱們更改的僅僅是狀態state, 而getters mutations actions仍舊使用的時舊的狀態,這個是在安裝時決定的,所以咱們還須要讓他們在每次執行的時候可以拿到最新的狀態
    }
    get state() {// 獲取實例上的state屬性就會執行此方法
        return this.vm.state
    }
    commit = (mutationName, payload) => {// es7寫法, 這個裏面的this永遠指向的就是當前store實例
        // this.mutations[mutationName](payload);
        this.mutations[mutationName].forEach(mutation => mutation(payload));
    }
    dispatch = (actionName, payload) => {
        // this.actions[actionName](payload);
        this.actions[actionName].forEach(action => action(payload));
    }
    // 動態註冊
    registerModule(moduleName, module) {
        if (!Array.isArray(moduleName)) {
            moduleName = [moduleName];
        }
        this.modules.register(moduleName, module); // 這裏只作了格式化
        installModule(this, this.state, moduleName, module.rawModule);// 從當前模塊開始安裝
    }
}
const install = (_Vue) => {
    Vue = _Vue;

    // 放到原型上不對,由於默認會把全部Vue實例都添加$store屬性
    // 咱們想要的是隻從當前的根實例開始,到他全部的子組件都有$store屬性

    Vue.mixin({
        beforeCreate() {
            // console.log('這是mixin中的1', this.$options.name);
            
            // 把根實例的store屬性放到每個組件中
            if (this.$options.store) {
                this.$store = this.$options.store;
            } else {
                this.$store = this.$parent && this.$parent.$store;
            }
        }
    });// 抽離公共的邏輯

}

export function mapState (stateArr) {// {age: fn}
    let obj = {};
    stateArr.forEach(stateName => {
        obj[stateName] = function() {
            return this.$store.state[stateName];
        }
    });
    return obj;
}

export function mapGetters(gettersArr) {
    let obj = {};
    gettersArr.forEach(getterName => {
        obj[getterName] = function() {
            return this.$store.getters[getterName];
        }
    });
    return obj;
}

export function mapMutations(obj) {
    let res = {};
    Object.entries(obj).forEach(([key, value]) => {
        res[key] = function(...args) {
            this.$store.commit(value, ...args);
        }
    });
    return res;
}
export function mapActions(obj) {
    let res = {};
    Object.entries(obj).forEach(([key, value]) => {
        res[key] = function(...args) {
            this.$store.dispatch(value, ...args);
        }
    });
    return res;
}
export default {
    install,
    Store
}
複製代碼
相關文章
相關標籤/搜索