當專案越來越大時,Vuex 允許我們將 store 切分成 modules 來方便管理,並且每個 module 都有自己的 state、mutations、getters、actions:
const moduleA = {
// 注意 state 是個 function
state: () => ({...}),
getters: {...},
mutations: {...},
actions: {...},
};
const moduleB = {
state: () => ({...}),
getters: {...},
mutations: {...},
actions: {...},
};
const store = createStore({
state: {...},
getters: {...},
mutations: {...},
actions: {...},
modules: {
moduleA,
moduleB,
},
});
而調用 modules 裡面的資料時只要在原本的 state 後面接上 module.module 的 state
:
const moduleA = {
state: () => ({
text: 'I'm a module.',
}),
};
const store = createStore({
modules: {
moduleA,
},
});
// 調用 moduleA 的 state 裡的 text
store.state.moduleA.text;
調用 getters、mutations、actions 則和之前的寫法一樣:
const moduleA = {
state: () => ({
text: 'moduleA',
}),
getters: {
reverseText(state) {
const textArr = state.text.split('');
return textArr.reverse().join('');
},
},
mutations: {
addExclamationMark(state) {
state.text = `${state.text}!`;
},
},
actions: {
async fetchRandomUser() {
const json = await fetch('https://randomuser.me/api/')
.then((res) => res.json());
console.log(json);
},
},
};
const store = createStore({
modules: {
moduleA,
},
});
// 調用 moduleA 裡面的 getters、mutations、actions
store.getters.reverseText;
store.commit('addExclamationMark');
store.dispatch('fetchRandomUser');
使用 mapState 導入 modules 的 state
我們可以使用 mapState 裡面傳入物件並搭配 function 來導入 modules 的 state:
computed: {
...mapState({
text: (state) => state.moduleA.text,
}),
},
而 mapGetters、mapMutations、mapActions 和之前的用法一樣。
取得外層 store 物件的 state 和 getters
在 module 裡面 getters 和 actions 都可以取得外層的 state,getters 還可以取得外層的 getters,只要在 getters、actions 的方法裡面帶上第三個參數 rootState
就可以取得外層的 state,而在 getters 帶上第四個參數 rootGetters
,則可以取得外層 getters:
const moduleA = {
getters: {
getRootState(state, getters, rootState, rootGetters) {...},
},
actions: {
fetch(context) { context.rootState... },
// 也可以使用物件解構 { rootState }
},
};
Namespacing
在上面的範例裡面我們可以發現調用 modules 的 getters、mutations、actions 時,不用像調用 state 時一樣加上 module 對應的名稱,如果我們這時在外層與 modules 裡面的 getters、mutations、actions 各有一個相同的名稱的方法,那結果會怎樣?
const moduleA = {
state: () => ({
text: 'moduleA',
}),
getters: {
reverseText(state) {
const textArr = state.text.split('');
return textArr.reverse().join('');
},
},
mutations: {
commitText(state) {
state.text += '!';
},
},
actions: {
action() {
console.log('module action');
},
},
};
const store = createStore({
state: {
text: 'store',
},
getters: {
reverseText(state) {
const textArr = state.text.split('');
return textArr.reverse().join('');
},
},
mutations: {
commitText(state) {
state.text += '!';
},
},
actions: {
action() {
console.log('store action');
},
},
});
store.getters.reverseText;
store.commit('commitText');
store.dispatch('action');
結果會是都有執行,並且 getters 會出現 [vuex] duplicate getter key: reverseText
的錯誤:
store.getters.reverseText;
// 出現 getters 重複 key 的錯誤
// [vuex] duplicate getter key: reverseText
// 並會印出外層 store 的 getters:erots
store.commit('commitText');
// 使用 dev tools 查看會發現兩個 text 都會被加上 !
store.dispatch('action');
// 分別印出 store action 與 moduleA action
會出現這樣的結果是因為在預設狀態下 getters、mutations、actions 都是註冊在全域底下。
那如果我就是想要有重複的命名但又想避免這樣的錯誤呢?
Vuex 也有提供方法來解決,只要在 modules 裡面加上 namespaced: true
:
const moduleA = {
namespaced: true,
};
這樣 Vuex 就會自動幫我們在 getters、mutations、actions 前面根據 modules 的名稱加上對應的路徑,而我們要調用時就會變成下面這樣:
store.getters['moduleA/reverseText'];
store.commit('moduleA/commitText');
store.dispatch('moduleA/action');
而 Namespace 一樣可以用 mapGetters、mapMutations、mapActions 來導入:
computed: {
...mapGetters([
'moduleA/reverseText', // this['moduleA/reverseText']
]),
},
methods: {
...mapMutations([
'moduleA/commitText', // this['moduleA/commitText']
]),
...mapActions([
'moduleA/action', // this['moduleA/action']
]),
},
如果覺得上面這樣寫太長太麻煩,我們可以在 mapGetters、mapMutations、mapActions 的第一個參數傳入 module 的路徑名稱:
computed: {
...mapGetters('moduleA', [
'reverseText', // this.reverseText
]),
},
methods: {
...mapMutations('moduleA', [
'commitText', // this.commitText'
]),
// 假設 moduleA 裡面有 moduleB,並且要導入 moduleB 的 actions
...mapActions('moduleA/moduleB', [
'action', // this.action
]),
},
你也可以用 Vuex 提供的 createNamespacedHelpers
來快入導入特定 module 裡面的 getters、mutations、actions:
import { createNamespacedHelpers } from 'vuex';
const {
mapState,
mapActions,
mapGetters,
mapMutations,
} = createNamespacedHelpers('moduleA');
export default {
computed: {
...mapState(['text']), // moduleA.text
...mapGetters(['reverseText']), // moduleA/reverseText
},
methods: {
...mapActions(['fetchRandomUser']), // moduleA/fetchRandomUser
...mapMutations(['commitText']), // moduleA/commitText
},
};
調用外層 store 的 commit、dispatch
如果想要在 module 裡使用外層的 mutations 或是 actions,我們只要在調用時傳入第三個參數 { root: true }
:
const moduleA = {
actions: {
commitAndDispatchRoot({ commit, dispatch }) {
dispatch('fetchSomething', null, { root: true });
commit('commitSomething', null, { root: true });
},
},
};
const store = createStore({
mutations: {
commitSomething() {
...
},
},
actions: {
fetchSomething() {
...
},
},
});
參考資料
- Vuex 官方文件
- 許國政(Kuro),《重新認識 Vue.js:008 天絕對看不完的 Vue.js 3 指南》