從理解源碼開始學習Vuex(二)

前言

經過筆者上篇文章的介紹,相信你們對Vuex的基本實現邏輯應該有了一個大概的瞭解,今天筆者想要介紹的呢就是這一個Vuexmodules部分。javascript

這個時候就有人問了,modules部分爲啥不和上文一塊兒介紹完呢?其實當時筆者也有想過一篇文章把Vuexstategettersmutationsactions以及今天的重點modules一塊兒捋一遍,不過考慮到加上modules以後,會對Vuex基本結構部分也就是state|getters...的具體原理實現部分產生理解上的影響,因而呢,筆者就把modules部分單獨抽出來給你們分析一遍,至於爲何會有必定影響,請看下文分解😀。java

Vuex源碼分析之 modules

因爲使用單一狀態樹,應用的全部狀態會集中到一個比較大的對象。當應用變得很是複雜時,store 對象就有可能變得至關臃腫。數組

爲了解決以上問題,Vuex 容許咱們將 store 分割成模塊(module)。每一個模塊擁有本身的 state、mutation、action、getter,甚至是嵌套子模塊——從上至下進行一樣方式的分割。模塊化

具體關於modulesVuex中的用法以及應用場景在官方文檔中發已經介紹的比較清晰了,筆者在這就不詳述了,今天咱們的重點仍是來研究研究它的具體實現。函數

ModuleCollection

Vuex中,有這樣的一個類專門用於格式化用戶傳入的參數對象,使其變成咱們方便操做的結構,顧名思義就是模塊收集,下面咱們將詳細介紹它的實現方法。源碼分析

其實從這個地方開始,咱們的構造函數內部已經產生了比較大的變化,對state|getters...的處理也沒有那麼簡單的只有一層結構了。網站

constructor(options) {
        /**借用Vue的雙向綁定機制讓Vuex中data變化實時更新界面 */
        this.vm = new _Vue({
            data: {
                state: options.state
            }
        })
        /**保存一份到自己實例 */
        this._options = options;
        /**保存getters */
        this.getters = {};
        this.mutations = {};
        this.actions = {};

        /**格式化用戶傳入的數據 */
        this.modules = new ModuleCollection(this._options)
        installModule(this, this.state, [], this.modules.root)
        // let getters = this._options.getters || {}
        /**遍歷保存傳入的getters,監聽狀態改變從新執行該函數 */
        // forEach(getters, (getterName, fn) => {
        // Object.defineProperty(this.getters, getterName, {
        // get: () => {
        // return fn(this.state)
        // }
        // })
        // })

        /**保存mutations */
        // this.mutations = {};
        // let mutations = this._options.mutations || {};
        // forEach(mutations, (mutationName, fn) => {
        // this.mutations[mutationName] = (payload) => {
        // return fn(this.state, payload)
        // }
        // })
        // this.actions = {};
        /**保存actions */
        // 
        // let actions = this._options.actions || {};
        // forEach(actions, (actionName, fn) => {
        // this.actions[actionName] = (payload) => {
        // return fn(this, payload)
        // }
        // })
    }
複製代碼

爲了造成對比,筆者這裏把上節的代碼註釋掉放在下面做爲比較,細心的小夥伴應該能發現這個構造函數的內部已經被分化成兩個主要模塊,一個是模塊收集,一個就是模塊安裝,這裏咱們先介紹一下這個ModuleCollection類。ui

好比咱們通常會傳入這樣的Store參數:this

{
    state:{},
    getters:{},
    modules:{
        a:{
            state:{
                test:""
            },
            modules:{
                c:{
                    state:{},
                    modules:...
                }
            }
        }
    }
    }
}
複製代碼

上面這種結構就是典型的帶模塊化的結構,咱們通常在獲取模塊中的值的時候是這樣的:this.$store.state.a.test,那麼爲了能實現這種取值方式,下面的模塊收集格式化就變得十分重要了。spa

其實下面這個部分能夠理解成三部分:

  • 先造成模板對象
  • 而後將它掛載到對應的層級上
  • 最後查詢是否還有子模塊,若是有就遞歸繼續重複上述步驟。
class ModuleCollection {
    constructor(rootModule) {
        this.register([], rootModule)
    }
    register(path, rootModule) {
        /*改造結構爲咱們的目標對象*/
        let newModule = {
            _raw: rootModule,
            _children: rootModule.modules,
            state: rootModule.state
        }
        
        /*掛載對象*/
        if (path.length == 0) {
            this.root = newModule;
        } else {
            let parent = path.slice(0, -1).reduce((pre, cur) => {
                return pre._children[cur]
            }, this.root)
            parent._children[path[path.length - 1]] = newModule;
        }
        
        /*遞歸查詢*/
        if (rootModule.modules) {
            forEach(rootModule.modules, (moduleName, value) => {
                this.register(path.concat(moduleName), value)
            })
        }
    }
}
複製代碼

注:上面的forEach函數在上文已經有介紹,這裏就不繼續介紹了。

其實這個部分核心的思想就是遞歸查找全部的層級的modules,而後所有格式化咱們想要的結構,下面咱們一步步分析這個代碼結構。

let newModule = {
    _raw: rootModule,
    _children: rootModule.modules,
    state: rootModule.state
}
複製代碼

首先咱們在開頭給出了這樣一個模板對象,也就是咱們但願格式化後的對象中每一個層級應該是這樣的結構。_raw對象保留了當前層級模塊的引用,_children屬性保存了當前層級下的modules對象的引用,而後咱們額外保留了一個屬性state,方便咱們後續的操做。

咱們須要明確一點就是,Vuex的機制是,若是你沒有設置namespaced:true,那麼你的state|mutations...都會被掛載到根節點上。

而後再往下看:

if (path.length == 0) {
    this.root = newModule;
} else {
    let parent = path.slice(0, -1).reduce((pre, cur) => {
        return pre._children[cur]
    }, this.root)
    parent._children[path[path.length - 1]] = newModule;
}
複製代碼

其實這個地方不太好理解,咱們能夠這麼去看這個部分,好比a.b.c,咱們能夠先取出前面兩層,也就是a.b,而後再將c定義到這個對象上去。這個部分利用的是reduce能返回前一項的計算結果的特性加以實現咱們想要的效果。

這個部分其實主要的目的就是若是是根模塊就直接掛載到root上,不然就是子模塊。最後進行子模塊查詢,遞歸依次掛載並造成咱們想要的結構。

這個部分其實有點晦澀的地方應該就是那個中間reduce部分了,其餘應該理解起來還好...吧😂。

installModule

格式化完了,天然就是須要進行咱們最重要的一步啦,掛載這個對象🧐。

首先咱們也是先捋一遍思路:

  • 掛載state|getters|mutations|actions
  • 查詢是否還有子模塊,若是有,遞歸併將各項屬性都定義到根模塊相應的state...上去。

掛載getters

let getters = rootModule._raw.getters;
if (getters) {
    forEach(getters, (getterName, fn) => {
        Object.defineProperty(store.getters, getterName, {
            get() {
                return fn(state)
            }
        })
    })
}
複製代碼

這裏的rootModule指的就是當前層級的模塊對象,先獲取當前級有沒有getters,若是有就掛載到根模塊上。

掛載mutations、actions

爲何要把這兩個放在一塊兒講呢,額這個,由於他們的實現部分真差很少😅。 這裏咱們也須要明確一點Vuex的機制,若是有同名的mutaitions或者actions他們會依次執行,因此保存他們的應該是一個數組。

/*motations部分*/
let mutations = rootModule._raw.mutations;
if (mutations) {
    forEach(mutations, (mutationName, fn) => {
        store.mutations[mutationName] || (store.mutations[mutationName] = []);
        store.mutations[mutationName].push((payload) => {
            fn(state, payload)
        })
    })
}
/*actions部分*/
let actions = rootModule._raw.actions;
if (actions) {
    forEach(actions, (actionName, fn) => {
        store.actions[actionName] || (store.actions[actionName] = []);
        store.actions[actionName].push((payload) => {
            fn(store, payload)
        })
    })
}
複製代碼

首先咱們會在根模塊上找是否已經有了保存當前函數的數組對象,若是有就直接拿出來,沒有就初始化一個空對象,也就是同名函數都將保存到當前數組中等待調用。

這個時候假設咱們能看到這樣一個根模塊上的結構:

{
    getters:{},
    state:{},
    mutations:{
        a:[fn1,fn2...]
    },
    actions:{
        b:[fn1,fn2...]
    }
}
複製代碼

而後咱們的commit|dispatch方法也要相應的修改一下啦。

commit = (type, payload) => {
    this.mutations[type].forEach(fn => fn(payload));
}
dispatch = (type, payload) => {
    this.actions[type].forEach(fn => fn(payload));
}
複製代碼

這裏就不能單獨的調用了,而是要遍歷調用。

查找子模塊,遞歸執行以上步驟

這個其實原理也是比較簡單的:

if (rootModule._children) {
    forEach(rootModule._children, (moduleName, module) => {
        installModule(store, module.state, path.concat(moduleName), module)
    })
}
複製代碼

其實就是遍歷出子模塊的每一個state|gettes|...

掛載state

這裏可能又有小夥伴要問了,爲啥把這個寫在最後呢?咳咳,好吧,這個可能相對來講會複雜一點點,也是考慮到a.b.c這種模塊之間的層級問題,因此呢,咱們又須要用到reduce來進行嵌套定義。

if (path.length > 0) {
    let parent = path.slice(0, -1).reduce((pre, cur) => {
        return pre[cur]
    }, store.state)
    /**利用Vue set方法實現數據綁定 */
    _Vue.set(parent, path[path.length - 1], rootModule.state)
}

複製代碼

這裏其實也是和上面差很少的,這個path是一個數組,最後一位元素表明當前模塊對象,因此咱們須要用前面的參數找到它的父級,而後再把它定義到它對應的層級位置。

最後貼上installModule的所有代碼:

/**安裝模塊 */
const installModule = (store, state, path, rootModule) => {
    if (path.length > 0) {
        let parent = path.slice(0, -1).reduce((pre, cur) => {
            return pre[cur]
        }, store.state)
        /**利用Vue set方法實現數據綁定 */
        _Vue.set(parent, path[path.length - 1], rootModule.state)
    }

    let getters = rootModule._raw.getters;
    if (getters) {
        forEach(getters, (getterName, fn) => {
            Object.defineProperty(store.getters, getterName, {
                get() {
                    return fn(state)
                }
            })
        })
    }

    let mutations = rootModule._raw.mutations;
    if (mutations) {
        forEach(mutations, (mutationName, fn) => {
            store.mutations[mutationName] || (store.mutations[mutationName] = []);
            store.mutations[mutationName].push((payload) => {
                fn(state, payload)
            })
        })
    }

    let actions = rootModule._raw.actions;
    if (actions) {
        forEach(actions, (actionName, fn) => {
            store.actions[actionName] || (store.actions[actionName] = []);
            store.actions[actionName].push((payload) => {
                fn(store, payload)
            })
        })
    }

    if (rootModule._children) {
        forEach(rootModule._children, (moduleName, module) => {
            installModule(store, module.state, path.concat(moduleName), module)
        })
    }
}
複製代碼

所有源碼

let _Vue;
/**簡化代碼,封裝遍歷方法 */
const forEach = (obj, callback) => {
    Object.keys(obj).forEach((key) => {
        callback(key, obj[key])
    })
}

class ModuleCollection {
    constructor(rootModule) {
        this.register([], rootModule)
    }
    register(path, rootModule) {
        let newModule = {
            _raw: rootModule,
            _children: rootModule.modules,
            state: rootModule.state
        }

        if (path.length == 0) {
            this.root = newModule;
        } else {
            let parent = path.slice(0, -1).reduce((pre, cur) => {
                return pre._children[cur]
            }, this.root)
            parent._children[path[path.length - 1]] = newModule;
        }

        if (rootModule.modules) {
            forEach(rootModule.modules, (moduleName, value) => {
                this.register(path.concat(moduleName), value)
            })
        }
    }
}

/**安裝模塊 */
const installModule = (store, state, path, rootModule) => {
    if (path.length > 0) {
        let parent = path.slice(0, -1).reduce((pre, cur) => {
            return pre[cur]
        }, store.state)
        /**利用Vue set方法實現數據綁定 */
        _Vue.set(parent, path[path.length - 1], rootModule.state)
    }

    let getters = rootModule._raw.getters;
    if (getters) {
        forEach(getters, (getterName, fn) => {
            Object.defineProperty(store.getters, getterName, {
                get() {
                    return fn(state)
                }
            })
        })
    }

    let mutations = rootModule._raw.mutations;
    if (mutations) {
        forEach(mutations, (mutationName, fn) => {
            store.mutations[mutationName] || (store.mutations[mutationName] = []);
            store.mutations[mutationName].push((payload) => {
                fn(state, payload)
            })
        })
    }

    let actions = rootModule._raw.actions;
    if (actions) {
        forEach(actions, (actionName, fn) => {
            store.actions[actionName] || (store.actions[actionName] = []);
            store.actions[actionName].push((payload) => {
                fn(store, payload)
            })
        })
    }

    if (rootModule._children) {
        forEach(rootModule._children, (moduleName, module) => {
            installModule(store, module.state, path.concat(moduleName), module)
        })
    }
}

class Store {
    constructor(options) {
        /**借用Vue的雙向綁定機制讓Vuex中data變化實時更新界面 */
        this.vm = new _Vue({
            data: {
                state: options.state
            }
        })
        /**保存一份到自己實例 */
        this._options = options;
        /**保存getters */
        this.getters = {};
        this.mutations = {};
        this.actions = {};

        /**格式化用戶傳入的數據 */
        this.modules = new ModuleCollection(this._options)
        installModule(this, this.state, [], this.modules.root)
        console.log(this)
        // let getters = this._options.getters || {}
        /**遍歷保存傳入的getters,監聽狀態改變從新執行該函數 */
        // forEach(getters, (getterName, fn) => {
        // Object.defineProperty(this.getters, getterName, {
        // get: () => {
        // return fn(this.state)
        // }
        // })
        // })

        /**保存mutations */
        // this.mutations = {};
        // let mutations = this._options.mutations || {};
        // forEach(mutations, (mutationName, fn) => {
        // this.mutations[mutationName] = (payload) => {
        // return fn(this.state, payload)
        // }
        // })
        // this.actions = {};
        /**保存actions */
        // 
        // let actions = this._options.actions || {};
        // forEach(actions, (actionName, fn) => {
        // this.actions[actionName] = (payload) => {
        // return fn(this, payload)
        // }
        // })
    }
    get state() {
        return this.vm.state
    }
    commit = (type, payload) => {
        this.mutations[type].forEach(fn => fn(payload));
    }
    dispatch = (type, payload) => {
        this.actions[type].forEach(fn => fn(payload));
    }
}
const install = (vm, options) => {
    _Vue = vm;
    _Vue.mixin({
        beforeCreate() {
            if (this.$parent) {
                this.$store = this.$parent.$store
            } else {
                this.$store = this.$options && this.$options.store
            }
        }
    })
}
export default {
    install,
    Store
};
複製代碼

說了這麼多,雖然有些地方的確有點晦澀隘口,不過筆者仍是相信聰明的大家仍是能理解的😂。固然,若是以爲本文對你還有點幫助的,就請給筆者點個贊吧。若是發現文中有不正確的地方,請猛戳筆者。

最後貼上筆者的我的網站:煙雨的我的博客

相關文章
相關標籤/搜索