手撕Vuex-提取模組資訊

BNTang發表於2023-11-08

前言

在上一篇【手撕Vuex-模組化共享資料】文章中,已經瞭解了模組化,與共享資料的注意點。

那麼接下來就要在我們自己的 Nuex 中實現共享資料模組化的功能。那麼怎麼在我們自己的 Nuex 中實現共享資料模組化的功能呢?

處理資料

也非常的簡單,是不是就是處理一下子模組的資料,處理一下子模組的 getters,處理下子模組的 mutations,處理下子模組的 actions 就可以了。

那麼怎麼處理呢?首先我們來看下資料怎麼處理。想要知道怎麼處理的,我們首先要知道資料是怎麼使用的。

資料怎麼使用的,我們在元件當中是不是拿到全域性的 Store, 拿到全域性的 Store 之後,從全域性的 store 中拿到子模組,然後從子模組中拿到資料,然後在元件當中使用。

所以說我需要怎麼做,我們需要將子模組的一個資料新增到全域性的 Store 當中,好了到這裡我們就已經瞭解了資料怎麼處理了。

那麼接下來我們就來看下 getters/ mutations/ actions 怎麼處理。

處理 getters

處理 getters 首先第一條就是,重名的方法不能不進行覆蓋。

處理 mutations

處理 mutations,在 mutations 當中,出現了同名的方法,那麼就不能不進行覆蓋。

如果說出現了同名的方法,那麼取值就是一個陣列,將所有的同名方法都新增到這個陣列當中。

然後執行這個同名方法就是迴圈這個陣列,然後執行這個陣列當中的每一個方法。

處理 actions

處理 actions,如果說出現了同名的方法,那麼取值就是一個陣列,將所有的同名方法都新增到這個陣列當中(同理可證)。

那麼知道了怎麼處理了之後,接下來怎麼辦呢?我們就來看下程式碼怎麼寫。如果我們直接處理傳遞進來的資料,可能呢,會比較麻煩,所以說在處理之前呢,我還需要將傳遞進來的資料進行一下子處理,按照我想要的格式進行格式化一下。

格式化資料

在實現之前,我先將我想格式的資料結構貼出來按照這個結構去編寫我們的格式化資料的方法。

let root = {
    _raw: rootModule,
    _state: rootModule.state,
    _children: {
        home: {
            _raw: homeModule,
            _state: homeModule.state,
            _children: {}
        },
        account: {
            _raw: accountModule,
            _state: accountModule.state,
            _children: {
                login: {
                    _raw: loginModule,
                    _state: loginModule.state,
                    _children: {}
                }
            }
        }
    }
}

那麼我們就來看下怎麼實現這個方法。

由於實現的方法程式碼比較繞,所以我這裡單獨開了一個類,來處理這件事情。

這個類的名字叫做 ModuleCollection,這個類的作用就是將傳遞進來的資料進行格式化,然後返回一個格式化之後的資料。

到這裡就要步入正題了,我們就來看下這個類的程式碼怎麼寫。

首先在 Store 類當中,將傳遞進來的資料傳遞到 ModuleCollection 類當中,然後在 ModuleCollection 類當中,將傳遞進來的資料進行格式化,然後返回一個格式化之後的資料。

編寫 ModuleCollection 類的程式碼:

class ModuleCollection {
    constructor(options) {
        this.register([], options);
    }

    register(arr, rootModule) {
        
    }
}

首先透過建構函式接收傳遞進來的資料,然後在建構函式當中,呼叫 register 方法,將傳遞進來的資料傳遞進去。

然後在 register 方法當中,接收兩個引數,第一個引數是一個陣列,第二個引數是一個物件。

第一個引數是一個陣列,這個陣列是用來儲存模組的名字的,第二個引數是一個物件,這個物件是用來儲存模組的資料的。

第一個引數是陣列也是我想用來區分是根模組還是子模組的,如果說是根模組,那麼這個陣列就是空的,如果說是子模組,那麼這個陣列就是有值的。

好了我們繼續走,第一步要處理的就是按照我們需要的格式建立模組,我定義了一個物件 module:

let module = {
    _raw: rootModule,
    _state: rootModule.state,
    _children: {}
}

如上雖然定義了模組資訊但是還沒有進行儲存起來,所以我們的第二步就是儲存模組資訊。

首先我將根模組進行儲存起來,子模組我們稍後再說。

// 2.儲存模組資訊
if (arr.length === 0) {
    // 儲存根模組
    this.root = module;
} else {
    // 儲存子模組
}

注意一下我所說的內容,我只是將根模組進行儲存起來,子模組還沒有進行儲存起來。

好,到這裡我們的第二部先告一段落,接下來我們就來看下第三步怎麼做。

第三步就是處理子模組,我們先來看下怎麼處理子模組。

首先我們要知道子模組的名字,這個可以透過迴圈根模組的 modules 屬性來獲取。知道了子模組的名稱之後,我們就可以透過子模組的名稱來獲取子模組的資料。從根模組的 modules 屬性當中透過子模組的名稱來獲取子模組的資料。

隨後我們就可以透過子模組的資料來建立子模組的模組資訊,然後將子模組的模組資訊進行儲存起來。

我們先將第三步的內容完成,程式碼如下:

for (let childrenModuleName in rootModule.modules) {
    let childrenModule = rootModule.modules[childrenModuleName];
    this.register(arr.concat(childrenModuleName), childrenModule)
}

如上程式碼的含義是,首先透過 for in 迴圈遍歷根模組的 modules 屬性,然後透過子模組的名稱來獲取子模組的資料,然後透過子模組的資料來建立子模組的模組資訊,然後將子模組的模組資訊進行儲存起來。

這裡就直接遞迴呼叫 register 方法,將子模組的名稱和子模組的資料傳遞進去。並且在遞迴呼叫的時候,將子模組的名稱新增到 arr 陣列當中。目的就是為了區分是根模組還是子模組。也是為了方便我們後續的操作(儲存子模組)。

好了到這裡我們的第三步也完成了,我們先將 arr 陣列進行列印,看下 arr 陣列的內容是什麼。

❗️注意:記得將官方的 Vuex 註釋掉,用我們自己的不然你會發現列印的內容和我們自己的不一樣。

列印結果如下圖:

[] 代表的是根模組,[home] 代表的是 home 模組,[account] 代表的是 account 模組,[account, login] 代表的是 account 模組下的 login 模組。

好了到這裡我們的第三步也完成了,接下來我們就來看下之前第二步沒有完成的內容怎麼完成。

基於第三步的列印結果分析出來各個模組的關係就可以根據這個關係得出各個模組的父子關係。按照這個關係就可以將子模組的模組資訊進行儲存起來。

我們先來看下程式碼怎麼寫,其實非常簡單我們先來分析一下,只需要分析 [account, login] 這種情況即可,我還是先將這種場景先不直接就給出答案,我們循序漸進的來,我們就走普通的邏輯,然後再來看下程式碼怎麼寫。我會直接往根模組的 children 屬性當中新增子模組的模組資訊。子模組資訊已經透過引數傳遞進來了,所以說我只需要將子模組的名稱獲取到即可,根據之前列印的結果來看,子模組的名稱是 arr 陣列當中的最後一個元素,所以說我只需要獲取 arr 陣列當中的最後一個元素即可。

然後將子模組的名稱作為 key,子模組的模組資訊作為 value,新增到根模組的 children 屬性當中即可。

程式碼如下:

this.root._children[arr[arr.length - 1]] = module;

好了到這裡我們的第二步也完成了,我們高高興興的要去列印結果了,結果如下:

發現 login 模組在 root 的 children 屬性當中了,login 模組應該在 account 模組的 children 屬性當中,所以說我們的程式碼還是有問題的。

誒,我們的程式碼有問題,那麼我們就來看下問題出在哪裡了。

問題出在我們不能直接往根模組的 children 屬性當中新增子模組的模組資訊,我們應該往父模組的 children 屬性當中新增子模組的模組資訊。

那麼我們怎麼知道父模組是誰呢?我們可以透過 root._children 來得到父模組,然後將子模組的模組資訊新增到父模組的 children 屬性當中即可。

程式碼如下:

let parent = arr.splice(0, arr.length - 1).reduce((root, currentKey) => {
    return root._children[currentKey];
}, this.root);
parent._children[arr[arr.length - 1]] = module;

讓我來逐步解釋:

  1. let parent = arr.splice(0, arr.length - 1):這一行程式碼從陣列 arr 中移除並返回除了最後一個元素之外的所有元素,將這些元素儲存在 parent 變數中。
  2. .reduce((root, currentKey) => { return root._children[currentKey]; }, this.root):這是一個 reduce 函式呼叫,它逐個遍歷 parent 陣列中的元素。root 是累積的結果,初始值是 this.rootcurrentKey 是每次迭代中的當前元素。
  3. parent._children[arr[arr.length - 1]] = module:最後一行程式碼將 module 賦值給 parent 物件的 _children 屬性中的某個屬性,該屬性的名稱來自陣列 arr 的最後一個元素。

如上是個簡單的解釋,接下來我套用資料來解釋一下,如下:

例如有這麼一個父子結構模組的陣列 let testArr = ['account', 'login'];,那麼 arr.splice(0, arr.length - 1) 就是將 testArr 陣列中的最後一個元素移除並返回除了最後一個元素之外的所有元素,將這些元素儲存在 parent 變數中,那麼 parent 變數中的值就是 ['account']

然後 reduce 函式呼叫,它逐個遍歷 parent 陣列中的元素。root 是累積的結果,初始值是 this.rootcurrentKey 是每次迭代中的當前元素。那麼 root._children[currentKey] 就是 this.root._children['account'],那麼 this.root._children['account'] 的值就是 account 模組的模組資訊。

最後一行程式碼將 module 賦值給 parent 物件的 _children 屬性中的某個屬性,該屬性的名稱來自陣列 arr 的最後一個元素。那麼 parent._children[arr[arr.length - 1]] 就是 account 模組的模組資訊,然後將 module 賦值給 account 模組的子模組,這樣我們的 login 模組就在 account 模組的 children 屬性當中了。

這個時候我們在來列印一下結果,結果如下:

好了這回就是我們想要的結果了,到此為止我們的 ModuleCollection 類就完成了。

相關文章