初見狀態管理工具 Vuex (3) Mutations、Actions


Posted by Calon on 2022-04-27

Mutations

唯一可以在 store 裡面更改 state 的方法是裡面去使用 commit 一個 mutation 來改變 state。

export default {
  state: {
    people: 1,
 },
  mutations: {
    increment(state) {
      state.people += 1;
    },
  },
};
// 元件內
export default {
  methods: {
    increment() {
      this.$store.commit('increment');
    },
  },
};

在 store 的 mutations 物件裡面新增一個 increment 方法,而 state 會作為第一個參數帶入。
而在使用 mutation 時,我們不是直接調用一個 mutation,而是類似觸發事件的方式來調用 mutations 裡面的 function,已範例來講:我們觸發一個 'increment' 的 mutation 時,會調用 store 裡的 increment 函式,所以在 commit 裡面要帶入相對應的字串。

接下來,我想要傳入參數去更改 state 的值要怎麼做?
很簡單,只要放在所要觸發的 mutation 的名稱後面就可以了:

this.$store.commit('increment', 14)

這個參數就叫做 palyload

mutations: {
  increment(state, playload) {
    state.people += playload;
  },
},

在大多情況下,playload 應該會是一個物件,也就是想傳入多個參數時,應該以物件的方式傳入:

this.$store.commit('increment', {
  num: 10,
  leader: 'Tom',
});
mutaions: {
  increment(state, playload) {
    state.people += playload.num;
  },
},

另外我們也可以在 commit 時使用物件的方式來傳入:

this.$store.commit({
  type: 'increment',
  num: 10,
  leader: 'Tom',
});

※若是使用 Vue 2.x 的版本,在還未定義 state 的狀態時,需要使用 Vue.$set(obj, '...', value),才能寫入新狀態,而從 Vue 3 開始就沒此規定,但建議還是要先定義好需要的 state 再去更新 state 的狀態,以減少意料之外的狀態。

Mutations 必須是「同步」函式

為了能清楚追蹤 state 的狀態,在 store 裡修改 state 狀態時只可透過 commit(提交)一個 mutation 來進行,如果使用了非同步函式因不知道什麼時候會回傳結果回來,就會導致狀態難以追蹤紀錄。

mapMutations

又見到 map 開頭的方法了。
沒錯,mutations 和 state、getter 一樣,可以使用 mapMutations 的方式方便我們在元件中快速導入多個 mutations,用法也大致相同:

import { mapMutations } from 'vuex'; // 一樣要記得先導入

methods: mapMutations([
  'toggleHungry',
  'increment',
]),
this.toggleHungry();

// 需要傳入 playload 時
this.increment(playload);

而要與其他 methods 結合時一樣使用物件展開的方式:

methods: { 
  ...mapMutations([
      'toggleHungry',
      'increment',
    ]),
},

可以使用物件方式帶入 mutations,並自定義 key:

mapMutations({
  toggle: 'toggleHungry',
  Increment: 'increment',
}),


Actions

上面提到因為 mutations 唯一能透過 commit 去修改 state 的狀態的方式,為了能追蹤到狀態,只能處理同步任務,所以 Vuex 提供了 actions 來處理非同步任務。
下面來串接 RANDOM USER GENERATOR 這個網站的 API 來寫一個簡單的範例:

export default createStore({
  state: {
    userData: {},
  },
  mutations: {
    setUserData(state, playload) {
      state.userData = playload;
    },
  },
  actions: {
    fetchUserData(context) {
      fetch('https://randomuser.me/api/')
        .then((res) => res.json())
        .then((res) => {
          context.commit('setUserData');
        });
    },
  },
});

在 actions 的方法裡第一個參數會帶入 context,如果我們用 console.log() 來查看 actions 的 context,會發現 context 是一個物件並且與 store 實體有著相同方法和屬性,但要注意它並不是 store 本身,因此我們可以用 commit 來觸發 mutation 的 setUserData(因為 state 只能用 commit 一個 mutation 來更改狀態,很重要所以在說一次!)去改變 state 的狀態。

既然 context 是一個物件,我們也可以用物件解構的方式來取得裡面的屬性:

actions: {
  fn({ commit }) {
    commit('mutation');
  },
},

接下來我們在元件內使用 dispatch(派發)來觸發 fetchUserData

this.$store.dispatch('fetchUserData');

// 要傳進去的資料帶入第二個參數
this.$store.dispatch('fetchUserData', 10);

// 也可以使用物件的形式傳入
this.$store.dispatch({
  type: 'fetchUserData',
  amount: 10,
});

如果剛才你有仔細看 context 裡面的內容的話會發現裡面也有一個 dispatch 屬性,所以我們也可以在一個 action 內 dispatch 另一個 action:

fetchUserData(context) {
  context.dispatch('anotherAction');
}

需要傳入參數的話只要在帶上第二個參數 playload,這裡的 playload 跟 mutations 的一樣是外面傳近來的參數:

actions: {
  fetchUserData(context, playload) {
    fetch('https://randomuser.me/api/')
      .then((res) => res.json())
      .then((res) => {
        context.commit('setUserData', { ...res, val: playload });
      });
  },
},

如果執行dispatch 回傳回來是一個 Promise 物件,我們也可以用 then 來鍊接:

actions: {
  test() {
    return fetch('https://randomuser.me/api/');
  },
},
this.$store.dispatch('test')
  .then((res) => res.json())
  .then((res) => {
    console.log(res);
});

也可以加上 asnyc / await

actions: {
  async test() {
    const res = await fetch('https://randomuser.me/api/');
    return res.json();
  },
  test2(context) {
    context.dispatch('test').then((res) => {
      console.log(res);
    });
  },
},

mapActions

前 3 組都有可以輕鬆導入多個資料、方法的方式,actions 當然也有囉,就是 mapActions:

import { mapActions } from 'vuex'; // 一定要記得導入

export default {
  methods: {
    ...mapAction(['fetchUserData']),
  },
};


// 或是物件的方式
methods: {
  ...mapAction({
    getData: 'fetchUserData'
  }),
},

參考資料
  • Vuex 官方文件
  • 許國政(Kuro),《重新認識 Vue.js:008 天絕對看不完的 Vue.js 3 指南》

#vuex #Vue.js







Related Posts

如何不使用 create-react-app 自己打造應用程式

如何不使用 create-react-app 自己打造應用程式

[http] 不只是簡單的跨域請求 Preflight Request

[http] 不只是簡單的跨域請求 Preflight Request

week4 作業繳交!

week4 作業繳交!


Comments