通過模擬vuex的核心原始碼快速掌握其原理

Liysuo發表於2019-07-30

通過模擬vuex的核心原始碼快速掌握其原理

為什麼要用vuex

官方回答:
Vuex 可以幫助我們管理共享狀態,並附帶了更多的概念和框架。這需要對短期和長期效益進行權衡。如果您不打算開發大型單頁應用,使用 Vuex 可能是繁瑣冗餘的。確實是如此——如果您的應用夠簡單,您最好不要使用 Vuex。一個簡單的 store 模式就足夠您所需了。但是,如果您需要構建一箇中大型單頁應用,您很可能會考慮如何更好地在元件外部管理狀態,Vuex 將會成為自然而然的選擇

與其他狀態管理庫對比:
Vuex 背後的基本思想,借鑑了 Flux、Redux 和 The Elm Architecture。與其他模式不同的是,Vuex 是專門為 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度資料響應機制來進行高效的狀態更新。(這也是官方給出的,待會我們原始碼中也會體現出這一優點)

我們在使用Vue.js開發複雜的應用時,經常會遇到多個元件共享同一個狀態,或者多個元件會去更新同一個狀態,在專案不復雜的情況下,我們可以元件間通訊或者是通過eventBus來維護資料。但是當應用逐漸龐大以後,程式碼就會變得難以維護,從父元件開始通過prop傳遞多層巢狀的資料由於層級過深而顯得異常脆弱,而事件匯流排也會因為元件的增多、程式碼量的增大而顯得互動錯綜複雜,難以捋清其中的傳遞關係。此時我們就需要用到vuex來為我們集中管理資料了。

Vuex

在說vuex原理之前,大家可以先結合下面的圖片想一想你在京東自營店購物是一個怎樣的流程,其實這個流程就跟我們們的vuex流程是一樣的, 比如你買一件衣服,你得先下單,下完單平臺接收到你的下單申請然後通知倉庫跟你發貨,你收到貨後開開心心的穿在了身上,感覺自己美美噠。vuex工作原理也是一樣,可以到我的github下載程式碼。

通過模擬vuex的核心原始碼快速掌握其原理

從圖中可以看出vuex實現的是一個單向資料流:
元件提交(commit) ->修改(在mutations中)->資料(state修改完成)->渲染到頁面(render),
要說的是action是vuex提供處理非同步資料的場所,使用action需要在元件內先派發一個dispatch通過action呼叫commit方法再修改state

vuex輪廓搭建

下面是vuex在專案裡使用的基本配置,我們們通過觀察下面的圖片一步一步的寫出我們自己的vuex

通過模擬vuex的核心原始碼快速掌握其原理
通過模擬vuex的核心原始碼快速掌握其原理

從上面圖中可以看到,使用vuex外掛要先Vue.use(),然後匯出了一個Store的類,類裡面傳入了,state、actions、getters、mutations物件

所以我們首先要匯出一個install方法和一個Store的類

let Vue;
class Store{ 
    constructor(options){ //option中就有 state getters mutations actions
        let state = options.state;
        this.getters = {};
        this.mutations = {};
        this.actions = {}
        // vuex也具備同樣的資料響應,這裡也是上面提到過的,vuex巧妙的運用的vue中data的資料響應
        // vuex核心就是借用了vue的例項 因為vue的例項資料變化 會重新整理檢視
        this._vm = new Vue({
            data:{
                state
            }
        });
        // 把模組直接的關係進行整理  自己根據使用者傳入的引數維護了一個物件 
        this.modules = new ModuleCollection(options);
        //  無論是子模組 還是孫子 所有的mutation 都是根上的
        // this是store的例項 [] path this.mdoue.root 當前的根模組
        installModule(this,state,[],this.modules.root); 
        let {commit,dispatch} = this;//拿到原型上面的 commit,dispatch方法 保證this指向的時store
        this.commit = (type) =>{
            commit.call(this,type);
        }
        this.dispatch = (type) =>{
            dispatch.call(this,type);
        }
    }
    get state(){ 
    // 這個方法可以理解為和Object.definefineProperty get是一個意思
    // 在元件中通過this.$Store.state.xxx訪問某個屬性時返回_vm中的state
        return this._vm.state;
    }
    commit(type){
        this.mutations[type].forEach(fn=>fn());
    }
    dispatch(type){
        this.actions[type].forEach(fn=>fn());
    }
}

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

let install = (_Vue) =>{
    Vue = _Vue; // 判斷Vue是否存在 優化重複use
    Vue.mixin({// 把根元件中 store例項 給每個元件都增加一個$store的屬性
        beforeCreate(){
            if(this.$options && this.$options.store){// 判斷是否是根元件
                this.$store = this.$options.store;
            }else{ // 子元件
                this.$store = this.$parent && this.$parent.$store
            }
        }
    })
}

export default {
    Store,
    install
}
    
複製程式碼

實現vuex的分模組機制

class ModuleCollection { // 預設actions mutation 都會定義到store上面
    constructor(options){ 
        this.register([],options);
    }
    register(path,rawModule){
        // path 是個空陣列 rawModule就是個物件
        let newModule = {
            _raw:rawModule,// 物件 當前 有state getters 那個物件
            _children:{}, // 表示 他包含的模組
            state:rawModule.state // 自己模組的狀態
        }
        if(path.length == 0){
            this.root = newModule; // 根
        }else{
            // [a,b];  // reduce方法 {{a.b.c}}
            let parent = path.slice(0,-1).reduce((root,current)=>{
                return root._children[current];
            },this.root);
            parent._children[path[path.length-1]] = newModule;
        }
        if(rawModule.modules){ // 有子模組
            forEach(rawModule.modules,(childName,module)=>{
                // [a,b];
                // [a,d]
                this.register(path.concat(childName),module)
            });
        }
    }
}
複製程式碼

執行你當前在元件中派發的方法

function installModule(store,rootState,path,rootModule){
    // rootState.a = {count:200}
    // rootState.a.b = {count:3000}
    if(path.length > 0){ 
         let parent = path.slice(0,-1).reduce((root,current)=>{
            return root[current];
         },rootState)
         Vue.set(parent,path[path.length-1],rootModule.state);
    }
    if(rootModule._raw.getters){
        forEach(rootModule._raw.getters,(getterName,getterFn)=>{
            Object.defineProperty(store.getters,getterName,{
                get:()=>{
                    return getterFn(rootModule.state);
                }
            });
        });
    }
    if(rootModule._raw.actions){
        forEach(rootModule._raw.actions,(actionName,actionFn)=>{
            let entry = store.actions[actionName] || (store.actions[actionName]=[]) ;
            entry.push(()=>{
                actionFn.call(store,store);
            })
        });
    }
    if(rootModule._raw.mutations){
        forEach(rootModule._raw.mutations,(mutationName,mutationFn)=>{
            let entry = store.mutations[mutationName] || (store.mutations[mutationName]=[]) ;
            entry.push(()=>{
                mutationFn.call(store,rootModule.state);
            })
        });
    }
    forEach(rootModule._children,(childName,module)=>{
        installModule(store,rootState,path.concat(childName),module);
    })
}
複製程式碼

到這裡簡版的vuex就全部實現了,若文字表述不清的可以到github上的下載在本地除錯,感覺寫的可以隨便點顆星哈哈。有哪裡寫的不足或者可以優化的地方可以評論交流一起學習。

相關文章