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 指南》