[Vue] Vuex 是什麼? 怎麼用? — State、Mutations (1/5)

itsems
itsems_frontend
Published in
12 min readApr 12, 2020
全系列共五篇:
一、[Vue] Vuex 是什麼? 怎麼用? — State、Mutations (1/5)
二、[Vue] Vuex 是什麼? 怎麼用? — Actions (2/5)
三、[Vue] Vuex 是什麼? 怎麼用? — Getters (3/5)
四、[Vue] Vuex 是什麼? 怎麼用? — Modules (4/5)
五、[Vue] Vuex 是什麼? 怎麼用? — 統整、專案結構(5/5)

Vuex官網

Outline:
1. Vuex 是什麼?為什麼要用Vuex?
2. 怎麼使用?(State、Mutations)

* 範例中 cli 版本為 cli3,在資料夾結構上 init 出來會跟 cli2 的人不太一樣,不影響使用

Vuex 是什麼?為什麼需要 Vuex?

在專案結構下通常會有多個組件,組件內可能又有組件,組件的溝通,通常會用到 emitpropsemit 回來, props 下去,

所以組件 B 和組件 C 之間的溝通,就會有超多 emitprops ,為了處理大型專案像是這樣跨結構的兄弟組件溝通,而非單純父子組件溝通,Vuex 就產生了。

Vuex 可以做為網站的全域狀態管理,可以將全域狀態集中管理,另一個很像的東西是 Event bus,但是 Event bus 比較像是一個全域的事件,組件 B 可以 emit 送出 bus 的事件或資料,組件 C 則是可以透過 on 來監聽 bus 發出的事件或接收資料。

所以 Event bus 比較適合用在沒有這麼複雜的情況,專案結構比較複雜的時候,則適用 Vuex。

怎麼使用?

Vuex 管理狀態的方法,可以先看一張官網的圖:

在 Vuex 裡面,儲存狀態的為 State,組件需要更動狀態時,需要透過 Actions 發出一個 Commit 去呼叫 Mutations,再由 Mutations 去更改 State。整個 Vuex 的方法也稱為 store。接下來再來一一說明整個步驟流程。

* 範例是用 vue cli 的方式說明,cdn 的話也可以使用 vuex 的 cdn 引入

1. Install

$ npm i -S vuex

2. Define Vuex

安裝好之後,在 /src 資料夾中新增一個 store.js 檔案,新增好之後先來建立一個 State 跟 Mutations:

這裡要注意 state 有一點像是 component 的 data,mutate 本身單字的意思是就是「變異」,人如其名就是拿來變動 state 用的。如果 mutations 要做更改,不可以變動在 State 還沒定義的 data

e.g,

如果要在 mutations 定義新的 state,可以像這樣寫:

3. Import

接下來在 main.js 裡面把 store 拉進來:

/main.js

import store from './store';new Vue({
store,
render: h => h(App),
}).$mount('#app')

4. Usage in component

在 component 中,把要顯示和動作的地方在 template 寫好:

<template>
<div>
<p>Loading: {{ifLoading}}</p>
<button @click="reverseLoad()">Reverse</button>
</div>
</template>

在 script 裡面:

template 裡面的 ifLoading 是 computed 裡面的 ifLoading 函式,會 return state 的 isLoading 狀態。button 的 click 事件 reverseLoad 則在 methods 裡面會呼叫 store commit Loaded 這個 mutations

簡單的 state 差不多就這樣了,

但是我如果有不只一個 state 呢?如果我還要看我點了幾次這個 button 呢?

以上面的做法就會變成:

在 store.js 再新增一個 state 跟 mutations :

/store.js

const store = new Vuex.Store({
state: {
isLoading: false,
clickedTimes: 0,
},
mutations: {
Loaded(state) {
state.isLoading = !state.isLoading;
},
addTimes(state) {
state.clickedTimes += 1;
},
}
})

/App.vue

template

<button @click="reverseLoad();addTimes()">Reverse</button>
<p>Button Clicked Times: {{clicked}}</p>

script

computed: {
ifLoading() {
return this.$store.state.isLoading;
},
clicked() {
// 抓 state 的 clickedTimes
return this.$store.state.clickedTimes;
}
},
methods: {
reverseLoad() {
this.$store.commit("Loaded");
},
addTimes() {
// commit 執行 addTimes 這個 mutations
this.$store.commit("addTimes");
}
}

很冗,如果再多幾個,就會開始很醜。

怎麼辦,不要怕,有解,Vuex 提供了一個叫做 mapState 的這個方法, mapState 有兩種方法可以使用:

  1. 陣列指定

這邊陣列裡面的字串 isLoading,就是 store 裡面的 state,所以 template 不能像剛剛那樣自己設不一樣的名字,要設定 store 裡面 state 的名稱

原本 computed 是一個物件,但是 mapState 也會回傳物件,所以可以直接這樣寫

2. 物件指定

如果你還是想要在 template 上設定一個不一樣的名字,就可以用物件的方式使用:

這裡的 isLoading 的地方除了是字串之外,也可以寫成函式:

computed: mapState({
ifLoading(state) {
return state.isLoading;
},
Times(state) {
return state.clickedTimes;
}

}),

因為沒有 this,也只有 state 一個參數,可以簡化為箭頭函式 (ES6):

computed: mapState({
ifLoading: state => state.isLoading,
Times: state => state.clickedTimes

}),

通常我們的 computed 裡面,不會只有 mapState,也會有別的 computed 要使用,可以使用 ES6 的 來達成:

computed: {
otherfn() {
return "asdf";
},
...mapState({
ifLoading: state => state.isLoading,
Times: state => state.clickedTimes
})
},

多個 mutations 也有 mapMutations 可以同理使用:

這樣就處理了多個 state 和 mutations 的問題了

另外 mutations 還有一個重點: payload

如果我今天想要點一次按鈕就加 2 次而不是加 1 次呢? 加 n 次呢?

在 store.js 中,先在 mutations 加上一個 payload 參數:

mutations: {
addTimes(state, payload) {
state.clickedTimes = state.clickedTimes + payload;
},
}

回到 App.vue,我們先把剛剛轉為 mapMutations 的 methods 轉換回來,然後在 commit 後面帶入要帶入的 payload

/App.vue

<template>
<div id="app">
<p>Loading: {{ifLoading}}</p>
<button @click="reverseLoad();add()">Reverse</button>
<p>Button Clicked Times: {{Times}}</p>
</div>
</template>
<script>
...,
computed: {
...
},
methods: {
add() {
return this.$store.commit("addTimes", 2);
},

}
};
</script>

commit 這邊原本只有 addTimes 這個字串,代表 store 的 mutations,帶入的第二個參數,會當作是 payload,commit 裡面也可以帶入物件:

addTimes() {
return this.$store.commit({
type: "addTimes",
count: 2
}
);
}

type 是必要的,表示 mutations 的名字,其他的都可以自定義,假設在這邊設定了一個 key 是 count,想要按鈕的次數是加上這個 count,在 store.js 裡面就要再加上:

addTimes(state, payload) {
state.clickedTimes = state.clickedTimes + payload.count;
},

就可以抓到在物件裡面設定的值了

一樣使用 mapMutations 的話,就把 payload 值直接在 template 使用時傳入,就可以了:

<button @click="Loaded();addTimes(2)">Reverse</button>

這樣就完成 mutations 的 payload 設定了 🏊🏊🏊

統整一下 State 和 Mutations 的寫法:

single State :

ifLoading() {
return this.$store.state.isLoading;
},

ifLoading 為 template 自定義,isLoading 則為 store 的 state 定義的

mapState:

一、陣列指定

...mapState(["isLoading", "clickedTimes"])

這邊的字串同等於 store.js 裡面的 state 名稱,上面的 template 也要取同名

二、物件指定

  1. 箭頭函式
...mapState({
ifLoading: state => state.isLoading,
})

ifLoading 可為 template 自定義,isLoading 則為 store.js 定義的 state

2. 字串

...mapState({
Times: "clickedTimes",
})

Times 為 template 自定義,clickedTimes 為 store.js 定義的 state

3. 函式

data() {
return {
name: "emma"
};
},
computed: {
...mapState({
myName(state) {
return state.myName + this.name;
}
})
},

這邊的 this.name 就是 component 的 this,就可以跟 component 的資料一起處理

single Mutations :

commit 函式

/app.vue

reverseLoaded() {
this.$store.commit("Loaded");
},
// String with payload
add() {
this.$store.commit("addTimes", 2);
}
// Object with payload
add() {
this.$store.commit({ type: "addTimes", key: 2 });
}

用物件傳送 object 的話,type 為必填,名字是要觸發的 mutations,其他的 key 都可以,但是在這邊定義了要加上的 payload 是 2,則 store.js 裡面的 addTimes mutations 要加上 state.clickedTimes = state.clickedTimes + payload.key;

mapMutations:

  1. 陣列字串
...mapMutations(["Loaded", "addTimes"])

在這邊的如果需要加上 payload,就要加在 template 上面: addTimes(2)

2. 物件

...mapMutations({
reverse: "Loaded",
add: "addTimes"
})

payload 一樣需要加在 template 上,reverseadd 則可由 template 定義

Mutations 只能做同步的操作,不可以做非同步的操作,如果需要非同步的操作,可以用 Actions,Actions 下篇見~

--

--

itsems
itsems_frontend

Stay Close to Anything that Makes You Glad You are Alive.