假如今天的專案有很多組件,並且有很多功能需要Vuex來管理, 那麼Vuex的State、Mutations、Getters、Actions可能會塞太多東西,變得臃腫龐大。

例如今天在store.js是這樣的 我們有很多功能像是代辦清單(todos)、購物車(cart)、產品(product)...etc 這些功能全寫進一個Vuex store,那麼store之後會變得太過攏長

import Vue from ‘vue';
import Vuex from ‘vuex’;

Vue.use(Vuex);

const store = new Vuex.Store({
 state:{
 todos:[ ],
 cart:[ ],
 product:[ ],
 //…etc
 },
 mutations:{
 //…etc
 },
//下略...
});

這種時候我們可以用Modules來幫我們適當地拆開、劃分不同Vuex物件, 把const store = new Vuex.Store改成這樣

const store = new Vuex.Store({
 modules:{
 //功能名稱(屬性名稱):額外抽出來指向的物件常數(當成modules屬性的值)
 }
});

我們在Vuex內使用Modules,Modules物件可以放自己取的名稱當屬性,而屬性「值」可以是物件, 這個屬性值(物件)等於是一個小型的Vuex Store,可以有自己的State、Mutations、Getters、Actions功能 這樣就可以模組化去寫很多會需要用到Vuex store的功能,在管理上也比方便。

所以我們需要宣告常數指向物件 (使用常數的目的是避免重複宣告被覆蓋掉) 再把這些物件引入自Vuex的store裡

在同一個store.js,新增常數去指向各自的物件

const todos = {
 state:{//略...},
 mutations:{//略...},
 getters:{//略...},
 actions:{//略...},
}
const cart = {
 state:{//略...},
 mutations:{//略...},
 getters:{//略...},
 actions:{//略...},
}
const product = {
 state:{//略...},
 mutations:{//略...},
 getters:{//略...},
 actions:{//略...},
}

這些物件有自己的State、Mutations、Getters、Actions,因為是額外拉出來寫的,單看這些物件好像沒什麼功用, 但這個時後把常數放進Vuex的屬性裡作為「值」

const store = new Vuex.Store({
 modules:{
 //功能名稱(屬性名稱):額外抽出來指向的物件常數(當成modules屬性的值)
 todos:todos,
 cart:cart,
 product:product
 }
});

這樣就好囉! 如果嫌常數全寫在store.js很麻煩,也可以額外拉出一個檔案寫, 總之最後import並讓常數(指向的物件)成為modules的物件屬性的值就好了

那我們要怎麼取用state呢? 例如現在todos指向的物件state有一個陣列list

const todos = {
 state:{
 list:[ ]
 },
 mutations:{//略...},
 getters:{//略...},
 actions:{//略...},
}

我們要在(.vue)組件取得Vuex.Store的modules的todos的陣列list,該如何取用?

app.vue


export default {
 computed:{
 todolist(){
 return this.$store.state.todos.list;
 }
 }
}

很簡單,一樣是this.$store.state開頭, 因為我們有把常數指向的物件引入Vuex,Vuex解讀時,會把這些Modules模組化的內容 如State、Mutations、Getters、Actions從最外頭的Vuex.Store開始查找 而我們要取用它,就需要帶入這個模組化物件的屬性名稱

所以要讀取物件todos內的state這個list,就直接

this.$store.state.模組化屬性名.要取用的屬性;

也就是

this.$store.state.todos.list;

這樣就好囉!

模組化的Mutations 每個常數指向的物件都有自己的State、Mutations、Getters、Actions 而mutations內的函式方法,第一個參數一樣是state, 第二個參數是plyload(plyload指的是從其他地方帶進來的參數例如commit)

const todos = {
 state:{
 list:[ ]
 },
 mutations:{
 setList(state,plyload){
 state.list[0] = 100;
 }
 },
 getters:{//略...},
 actions:{//略...},
}

要注意的是,這裡的state指的是todos物件裡的state屬性 而不是外層Vuex.Store的state或是其他Modules物件的state

模組化的Getters 我們在常數指向的物件、Getters裡使用自己定義得方法,例如這裡寫getLength和setLength

const todos = {
 state:{//略...},
 mutations:{//略...},
 getters:{

   getLength(state, getters){
    //略....
   }

   setLength(state, getters){
 //略....
 }
 },
 actions:{//略...},
}

第一個帶入的參數是state,是指todos這個物件的state沒錯 如果我們要用自己getters的其他方法(例如setLength),我們可以帶入getters當參數,然後getters.setLength 如果我們要用其他vuex模組化物件的state、外層vuexStore的state,我們可以帶入第三格參數rootState, 然後

rootState.其他Vuex模組屬性名.其他state的值

如果我們要用其他vuex模組化物件的getters、外層vuexStore的getters,我們可以帶入第三格參數rootGetters, 然後

rootGetters.其他Vuex模組屬性名.其他getters方法

也就是變成這樣

const todos = {
 state:{//略...},
 mutations:{//略...},
 getters:{
 getLength(state, getters,rootState,rootGetters){
 //略....
 }
 setLength(state, getters){
 //略....
 }
 },
 actions:{//略...},
}

模組化的Actions 我們在常數指向的物件、Actions裡使用自己定義得方法,例如這裡寫fetchList 傳入的參數是context,可是我們可以在函式方法的括號( )裡用{  }去做物件解構 取出我們要用的方法傳入就好

const todos = {
 state:{//略...},
 mutations:{//略...},
 getters:{//略...},
 actions:{
 fetchList({commit, dispatch, state,getters,rootState,rootGetters}){

    // 略.....
 }

 },
}

在Actions筆記我們提到其實傳入Actions的參數是context而不是store context包含store,store不等於context,這裡的例子就是: context有rootState、rootGetters兩個參數方法可以傳入,如果傳入Actions的參數是store,那麼就不會有這兩個方法了 因為原本的store並不包含rootState、rootGetters

模組化Vuex的命名相衝 如果今天模組化物件有相同的函式方法命名,然後我們在(.vue)組建呼叫這個命名重複的Vuex方法,會發生什麼事呢? 例如store.js

import Vue from ‘vue';
import Vuex from ‘vuex’;

const todos = {
 state:{//略...},
 mutations:{//略...},
 getters:{//略....},
 actions:{
 fetchList(context){
 // 去跟代辦清單要json
 }
 },
}

const member = {
 state:{//略...},
 mutations:{//略...},
 getters:{//略....},
 actions:{
 fetchList(context){
 // 去跟會員資料要json
 }
 },
}
Vue.use(Vuex);

const store = new Vuex.Store({
 modules:{
 todos:todos,
 member:member
 }
});

假如工程師沒注意到,碰巧todos和member的Vuex的actions裏取了兩個相同名稱的方法fetchList 然後再(.vue)組件去觸發 例如app.vue在mounted階段,DOM都載入後觸發Vuex內Actions的fetchList


export default {
 mounted(){
 this.$store.dispatch('fetchList')
 }
}

注:現在Vuex內有兩個fetchList 命名時沒報錯是因為他們名子都是在自己模組化內的物件內Actions屬性內取的名字, 再退一步說,不同物件的屬性名稱取一樣本來就不會怎樣,畢竟命名空間又不一樣, 只是今天物件都傳址給Vuex使用,所以才產生這種狀況!

所以會發生什麼事? 結果是兩個fetchList都會觸發執行!

為了要避免這種狀況,我們有兩種方式可以使用 1.方法名稱取不一樣 最直覺的解決方法,就命名時函式名稱取不一樣就好

2.命名空間namespaced設置true 在物件新增一個屬性namespaced,並且設置true

const todos = {
 namespaced:true,
 state:{//略...},
 mutations:{//略...},
 getters:{//略....},
 actions:{
 fetchList(context){
 // 去跟代辦清單要json
 }
 },
}

const member = {
 namespaced:true,
 state:{//略...},
 mutations:{//略...},
 getters:{//略....},
 actions:{
 fetchList(context){
 // 去跟會員資料要json
 }
 },
}

這樣Vuex就知道,啊這個命名空間注意一下 那麼要觸發這個相同名稱的Vuex函式,要怎麼去呼叫特定模組化物件的fetchList呢?

不管是commit( )或dispatch( )都是

('模組化物件/要呼叫的方法名稱')

類似我們用電腦檔案路徑的感覺 用/符號去間隔而不是.運算子 注意因為被' '上引號包著,所以還是傳入字串的形式 所以app.vue改成這樣

app.vue 呼叫todos的fetchList

export default {
 mounted(){
 this.$store.dispatch('todos/fetchList')
 }
}

呼叫member的fetchList

export default {
 mounted(){
 this.$store.dispatch('member/fetchList')
 }
}

模組Actions呼叫其他模組Action、Mutations 有個比較少見的狀況是,如果想在自己actions方法內,去呼叫別的模組actions、mutations方法,那應該怎麼做呢?

const todos = {
 state:{//略...},
 mutations:{//略...},
 getters:{//略...},
 actions:{
 fetchList({commit, dispatch, state,getters,rootState,rootGetters}){
 // 略.....
 },
 },
}

我們解構進去的參數有rootState、rootGetters,但卻沒有rootActions、rootMutations 這個時後,可以傳入第三個參數{root:true}

例如我在member模組物件這邊 想要在actions的fetchList內呼叫todos模組的mutations的setList方法

const todos = { 
 //略
 mutations:{
 setList(){   //<---觸發他
 //略
 }
 },
 //略...
 },
}

const member = { 
 mutations:{
  otherList(state){//略...}
 }
 actions:{
 fetchList({commit, dispatch, state,getters,rootState,rootGetters}){
 //commit('模組化物件/要呼叫的方法名稱',payload,{root:true})
 //注:payload是指傳入給該函式運用的參數
 commit('todos/setList',[1,2,3,4],{root:true})
 }
 }
}

當我們傳入{root:true}作為第三個參數,第一個參數就可以從vuex的store根目錄開始查找其他模組化的物件屬性,和該模組化的方法

commit('模組化物件/要呼叫的方法名稱',payload,{root:true})

所以這裡的

commit('todos/setList',[1,2,3,4],{root:true})

可以理解成,因為第三格參數傳了{root:true} 所以我們第一個參數可以從vuex的store根目錄開始查找,找到todos的mutations的setList名字並觸發他 但是,為什麼是找到mutations呢,很簡單,因為我們用commit呀!commit就是用來找mutations的!

當然我們在member的actions也可以這樣寫

commit('member/otherList',[1,2,3,4],{root:true})

呼叫自己mutations的otherList函式,因為第三個參數傳了{root:true},所以第一個參數名字要從根目錄開始找 這樣寫雖然也可以,但有點多此一舉 因為commit不傳第三個參數{root:true},那麼第一個參數-也就是函式名稱就是從該模組化物件開始找,也就是說要commit自己的mutations

commit('otherList',[1,2,3,4])

這樣寫就可以囉,結果是一樣的~

Vuex的模組化map方法與根目錄查找 那如果是(.vue)組件要用map方法,例如mapState,要怎麼去取用特定模組的state呢?

例如現在在app.vue,我們可以這樣寫

import { mapState } from ‘vuex’;

export default {
  computed:{
    ...mapState([
      'todos/list',
      'todos/text',
 'todos/input',
 'todos/val',
    ])
  }
};

但是這樣每個傳入mapState的陣列值都要字串todos開頭,這樣東西一多,等於每個都要'todo/xxxxx'非常麻煩

所幸有個方法可以解決這種問題 我們用mapState傳入的參數,把[ ‘名稱’,‘名稱’,‘名稱’]這種陣列改成第二個參數, 第一個參數就放入那個模組化物件的名稱

也就是改成這樣

import { mapState } from ‘vuex’;

export default {
 computed:{
 …mapState('todos', [
 'list',
 'text',
 'input',
 'val',
 ])
 }
};

第一個參數一樣是字串,但這樣方便多了,視同去todos模組查找state的list、text、input

而且我們用了...mapState將內容撒進computed物件,computed還可以寫其他方法或繼續用其他的...mapState 這種用法可以一次取用好幾種不同Vuex模組的state

例如也可以寫成這樣

import { mapState } from ‘vuex’;

export default {
 computed:{
 //取用todos的
 …mapState('todos', [
 'list',
 'text',
 'input',
 'val',
 ]),
 //也取用member的
 …mapState('member', [
 'loading',
 'number',
 'id',
 ]),
 //也取用cart的,這樣寫也可以
 …mapState(['cart/shop'])
 }
};

這樣方便多囉!