對 Vuex Module 的直觀理解

開始以前

本篇只是本人對 module 的學習總結和理解以及一些避免坑的意見,不必定能夠用在你的項目當中,即便你要使用,建議你先參考官方對比下先後文。
另外,module 是基於 vuex 即 store 狀態的模塊化管理方案,因此本篇是針對有過 store 使用經驗的同窗的一篇僅供參考的我的總結,若是你還不會 store 你得抓緊了! 或者你能夠參考 大宏說 老師的《Vuex白話教程第六講:Vuex的管理員Module(實戰篇)》javascript

模塊的局部狀態

模塊內部的 mutation 和 getter,接收的第一個參數是模塊的局部狀態對象:css

const moduleA = {
    state: { count:10 },
    getters:{
        filterCount(state){
            return state.count * 2// 20;
        }
    },
    mutations:{
        add(state){
            state.count * 2// 20;
        }
    },
}
複製代碼

action則是經過context.state暴露出來:html

actions:{
    incrementIfOddOnRootSum(context){
        context.state.count // 10
    }
}
複製代碼

模塊內部訪問全局的狀態

action 能夠經過rootState獲取到根節點的狀態:vue

actions:{
    incrementIfOddOnRootSum( { state, rootState } ){
        rootState.xx // 根節點的xx
    }
}
複製代碼

getter 接受根節點狀態是經過第三個參數暴露出來:java

getters:{
    sumWithRootCount(state, getters, rootState){
        //state 是模塊內部的狀態
        // getters 模塊內部的其餘getter
        // rootState 是全局的狀態
    }
}
複製代碼

命名空間的概念

若是模塊不使用命名空間的話,默認狀況下模塊內部的 getter, action 和 mutation是註冊在全局全局命名空間的,這樣的壞處是:web

store:{
    state:{
        count:18,
    },
    mutations:{
        setCount(state){
            state.count / 2;
            console.log(state.count) // 9
        }
    },
    modules:{
        a:moduleA
    }
}
moduleA:{
    state:{
        count:10,
    },
    mutations:{
        setCount(state){
            state.count * 2;
            console.log(state.count)//20
        }
    }
}
複製代碼

在提交 moduleA 的 mutation 時:vuex

this.$store.commit('setCount');
// 猜猜會打印啥?
// 9 和 20  這是由於前面所說的模塊內部的getter,action和mutation是註冊在全局全局命名空間的。
//因此上面的例子中全局命名空間裏有2個名爲setCount的mutation,而後他們都被觸發了。
複製代碼

開啓命名空間

想要讓模塊內部的 getter, mutation , action只做用域當前局部模塊內的話能夠給模塊添加namespaced屬性:api

modules:{
    moduleA:{
        namespaced:true,
        state:{...}, //state仍是僅做用於當前模塊內部
        getter:{
            isAdmin(){...}
        },
        mutations:{
            login(){...}
        },
        actions:{
            getToken(){...}
        }
    }
}
複製代碼

當開啓命名空間的模塊被註冊後它的全部 getter、action 及 mutation 都會自動根據模塊註冊的路徑調整命名,因此觸發路徑也有所改變:bash

store.getters['moduleA/isAdmin']; // 命名空間模塊內部的getter
store.dispatch('moduleA/getToken'); // 命名空間模塊內部的action
store.commit('moduleA/login'); // 命名空間模塊內部的mutation
複製代碼

命名空間模塊的嵌套

文檔裏也有說模塊內是還能夠嵌套模塊的大概意思就是:app

modules:{
    moduleA:{
        state:{...},
        mutations:{...},
        // 嵌套模塊
        modules:{
            mypage:{
                state:{...},
                getters:{
                    profile(state){}//由於嵌套模塊沒有本身命名空間,因此就自動繼承了父命名空間,因此就能夠這樣觸發這個getter:store.getters['moduleA/profile'];
                }
            },
            // 進一步嵌套命名空間
            posts:{
                namespaced:true,//開啓命名空間
                state:{...},
                getters:{
                    popular(){...}//前面咱們說過,開啓命名空間的模塊它全部的getter、action、mutation都會自動根據模塊的路徑調整命名 -> store.getters['moduleA/posts/popular']
                }
            }
        }
    }
}
複製代碼

嵌套模塊繼承命名空間後帶來的問題

接着上面繼承命名空間的例子:

modules:{
    moduleA:{
        state:{...},
        mutations:{...},
        // 嵌套模塊
        modules:{
            mypage:{
                state:{...},
                getters:{
                    profile(){...}
                }
            }
        }
    }
}
複製代碼

若是我如今要觸發profile這個getter我能夠這樣:

store.getters['moduleA/profile'];
複製代碼

由於即便是嵌套的模塊但mypage沒有本身的命名空間因此繼承了父命名空間,因此這樣觸發看上去沒有問題。

問題來了⚠️

若是父命名空間內也有一個名爲 profile 的getter:

modules:{
    moduleA:{
        state:{...},
        getters:{
            profile(state){...}
        }
        mutations:{...},
        // 嵌套模塊
        modules:{
            mypage:{
                state:{...},
                getters:{
                    profile(){...}
                }
            }
        }
    }
}
複製代碼

這個時候若是再執行:

store.getters['moduleA/profile'];
複製代碼

會是什麼結果呢?你們能夠本身動手試一試,加深一下印象。

帶命名空間的模塊和全局命名空間模塊的一些互動

獲取或修改全局狀態:

若是你但願使用全局 state 和 getter,rootState 和 rootGetters 會做爲第三和第四參數傳入 getter,也會經過 context 對象的屬性傳入 action。

modules: {
      foo: {
        namespaced: true,
        getters:{
            someGetter(state, getters, rootState, rootGettrers){
                // state 是當前模塊內部是狀態
                // getters 是當前模塊內部的 getters
                // rootState 是全局下的狀態
                // rootGettrers 是全局下的 gettrers
            }
        },
        actions:{
            someAction( { getters, rootGetters}){
                // getters 當前模塊內部的 getters,
                // rootGettrers 是全局下的 gettrers
            }
        }
      }
 }
複製代碼

若須要在全局命名空間內分發 action 或提交 mutation,將 { root: true } 做爲第三參數傳給 dispatch 或 commit 便可:

action:{
    someAction( { dispatch, commit}){
        dispatch('someOtherAction');// 分發當前模塊內名爲someOtherAction的action
        dispatch('someOtherAction'null, { root: true })// 分發全局名爲someOtherAction的action
        commit('someMutation'// 提交當前模塊內名爲someMutation的mutation
        commit('someMutation'null, { root: true }) // 提交全局名爲someMutation的mutation
    }
}
複製代碼

是的訪問全局只須要提供rootStaterootGetters參數就好,而分發action或提交mutation只須要將 { root: true }做爲第三個參數就好。

將模塊內部的action註冊到全局:

若業務須要在帶命名空間的模塊中註冊全局 action,你可添加 root: true,並將這個 action 的定義放在函數 handler 中。例如:

{
    actions: {
        someOtherAction ({dispatch}) {
            dispatch('someAction')
        }
    },
    modules: {
        foo: {
        namespaced: true,
        actions: {
            someAction: {
                  root: true,
                  handler (namespacedContext, payload) { ... } // -> 'someAction'
                }
            }
        }
    }
}
複製代碼

在組件中使用帶命名空間的模塊

官方給了兩種方案,這裏我使用命名空間輔助函數的寫法,另外一種寫法有興趣的同窗能夠去參考一下

這個例子官方其實已經給的很簡潔直觀了因此咱們直接看:

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // 在 `some/nested/module`上下文中 中查找
    ...mapState({
      astate => state.a,
      bstate => state.b
    })
  },
  methods: {
    // 在 `some/nested/module`上下文中 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}
複製代碼

這個例子我有一個疑問:若是mapStatemapActions是基於'some/nested/module'上下文的話,那若是我這個組件內還須要使用其餘命名空間的模塊該怎麼辦呢?有的同窗可能會說:

    const { mapState, mapActions } = createNamespacedHelpers('otherSome/nested/module')
複製代碼

再定義一個上下文不就行了嗎?但兩個上下文返回的都是一樣的mapStatemapActions,我在使用mapActions時,你怎麼知道我是在那個上下文中查找呢?

後來想了想我這個疑問是否成立?由於我以爲一個模塊store應該始終是效力於一個功能組件的。可是不保證沒有極端的狀況出現,若是真有這種需求的話,該怎麼實現?有經驗的同窗能夠教我一下。

動態註冊命名空間模塊

若是有業務需求須要咱們動態註冊模塊,咱們可使用 store.registerModule 方法註冊模塊:

// 註冊模塊 `myModule`
store.registerModule('myModule', {
  // ...
})
// 註冊嵌套模塊 `nested/myModule`
store.registerModule(['nested''myModule'], {
  // ...
})
複製代碼

動態註冊的前提是你的Store已經被你建立了。

以後就能夠經過store.state.myModulestore.state.nested.myModule訪問模塊的狀態。

卸載動態模塊

以後不須要模塊時可使用store.unregisterModule(moduleName)來動態卸載模塊來保證性能

注意:不能使用此方法卸載靜態模塊(即建立 store 時聲明的模塊)

模塊的複用

模塊重用,官方給的場景是:
一、建立多個store,他們共用同一個模塊
二、在一個store 中屢次註冊同一個模塊
說的可能比較抽象,咱們來一個簡單的例子:

moduleA.js

const moduleA = {
    state:{
        count:10
    },
    mutations:{
        changeCount(state){
            state.count = 20;
        }
    }
}
export default moduleA;
複製代碼

store.js

const store = new Vuex.Store({
    state:{...}
    mutations:{...},
    modules:{
        a:moduleA,
        b:moduleA,
        c:moduleA
    }
})
複製代碼

此時modules對象裏的a b c 都引用自moduleA模塊;
咱們再來建3個組件,而後分別引用a b c這3個模塊的實例:

test1.vue

<template>
    <div>
        {{count}}
    </div>
</template>
<script>
    import { createNamespacedHelpers } from 'vuex'
    const { mapState } = createNamespacedHelpers('a')
    export default {
        computed: {
            ...mapState({
                name:state => state.count
            })
        }
    }
</script>
複製代碼

test2.vue

<template>
    <div>
        {{count}}
    </div>
</template>
<script>
    import { createNamespacedHelpers } from 'vuex'
    const { mapState } = createNamespacedHelpers('b')
    export default {
        computed: {
            ...mapState({
                name:state => state.count
            })
        }
    }
</script>
複製代碼

test3.vue

<template>
    <div>
        {{count}}
    </div>
</template>
<script>
    import { createNamespacedHelpers } from 'vuex'
    const { mapState } = createNamespacedHelpers('c')
    export default {
            computed: {
                ...mapState({
                name:state => state.count
            })
        }
    }
</script>
複製代碼

3個組件的count都等於10,由於modulea b c 都引用自moduleA模塊;
此時,若是咱們在test1組件裏提交moduleA的mutation:

test1.vue

<template>
    <div>
        {{count}}
         <input type="button" value="click" @click="changeCount">

    </div>
</template>
<script>
    import { createNamespacedHelpers } from 'vuex'
    const { mapState,mapMutations } = createNamespacedHelpers('a')
    export default {
        methods:{
            ...mapMutations([changeCount])
        }
        computed: {
            ...mapState({
                name:state => state.count
            })
        }
    }
</script>
複製代碼

此時,只要咱們一提交changeCount,test1 test2 test3 組件裏的count都會被改成20;
緣由:

當一個模塊被定義,模塊可能被用來建立多個實例,這時若是模塊還是一個純粹的對象,則全部實例將共享引用同一個數據對象!這就是模塊間數據互相污染的問題。

解決模塊複用帶來的互相污染問題

爲了解決互相污染咱們可使用一個函數聲明來返回模塊的狀態:

const MyReusableModule = {
  state () {
    return {
      count: 10
    }
  },
  // mutation, action 和 getter 等等...
}
複製代碼

經過爲 state 聲明一個初始數據對象的函數,且每次建立一個新實例後,咱們可以調用 state 函數,從而返回初始數據的一個全新副本數據對象。

此番借鑑Vue 組件內的 data
大體意思就是讓state以函數聲明式返回狀態,這樣無論模塊被實例化多少次,每次實例化時模塊內部的state都會是一個全新的函數返回。

最後

給某些同窗一些建議:作筆記、總結這種東西必定是要你本身先學習一遍,而後理解事後的記錄,並不是是把人家文檔的東西循序漸進放到你的筆記當中,這樣作的意義何在呢?騙點擊的沙雕網友咱們就不評價了,並且人家文檔的東西始終是最新的,並且也有持續更新。複製 - 粘貼 - 發佈的這類沙雕網友拜託大家不要浪費你們的時間了。淨化學習環境從我作起!

固然一篇總結老是避免不了會用到原文檔的一些例子。

相關文章
相關標籤/搜索