假如今天的專案有很多組件,並且有很多功能需要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'])
}
};
這樣方便多囉!