Vue_Vuex狀態管理

Vue的組件溝通方式

在單純的情況之下,兩個上、下層Vue組件若要進行溝通,會使用:

1. props

由上傳下:上層使用 v-bind ,下層用props接到v-bind傳來的屬性資料
由下傳上:下層發出$emit事件,上層則用v-on偵聽該事件

2. eventbus

若專案更大一些,資料不太可能$emit一層一層慢慢傳上去,上層組件用v-on偵聽到事件並拿到資料之後,又再一層層用props慢慢傳到另一個下層組件,這樣很麻煩也很難維護,所以這種結構複雜的跨組件溝通會使用 eventbus 來傳遞資料。

Vuex狀態管理

Vuex 大致上是透過一個 Global state 來儲存整個網站共有的狀態,而其設計概念是由「購買按鈕」組件 commit 一個 Mutation,而這個 Mutation 會改變 State,當 State 被改變時,則會觸發有使用到 State 的「購物車」組件的視覺元件被更新,而這整個觸法的過程是「單向運作」的。

Vuex 跟 eventbus 最主要的差異在於,Vuex 的 Global state 是組件共用的狀態,而非單純儲存在某個組件內部中的狀態。所以本質上,組件與組件之間並沒有溝通,而是透過更新 Global State的狀態,而反應畫面到另外一個組件上。

使用 Vuex 時機

  • 組件之間很上互相溝通:不用Vuex
  • 組件僅需上下傳遞就很夠用:不用Vuex
  • 組件會互相溝通,但情境很固定,次數也很少:Event Bus
  • 組件之間會跨結構傳遞狀態:Vuex
  • 需要全域狀態管理:Vuex

vuex 的 Store 中有四大元素:

  • State
  • Mutations
  • Getters
  • Actions

解析彼此關係

Actions是指狀態的改變就像是Methods的函式,當State的變數需要做更動時,Actions是無法直接變更的,需經由代理商mutation當作兩者的媒介。

案例:我們可以看到State跟Actions裡的資料完全沒有一點瓜格,都是透過Mutations來連結彼此,逐一解釋三者關聯,

  • 在Mutations裡先命名LOADING為此橋樑的名稱,參數裡載入兩個位置(state預設, actions參數),以此將兩者串連。
  • actions也要先命名函式名稱updateLoading,並增加兩個參數,前者是函式內使用的變數,第二則成為payload是傳入mutation裡的參數,內容我們先宣告變數名稱要串連mutations的橋樑名稱,以及順帶將payload參數也傳入即可完成
  • 執行流程:html裡寫入vm.$store.dispatch(‘updateLoading’, true),內容主要是呼叫action函式名,並且載入參數true,這時action函式就會進行動作,傳入mutations執行並改變state裡的值,也就是status參數。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default new Vuex.Store({
state: {
isLoading: false,
},
//操作行為
actions:{
updateLoading(context, status) {
context.commit('LOADING', status);
},
},
//操作狀態
mutations:{
LOADING(state, status) { //第二個參數是外部傳入
state.isLoading = status;
},

}
});

主頁面使用dispatch將需求傳至action裡,並帶指定參數

1
vm.$store.dispatch('updateLoading', true);

如果有要回傳變數,則新增computed裡當作state的接口

1
2
3
4
computed: {
cart() {
return this.$store.state.cart;
},

存放狀態的 State

state 是 Vuex.Store 中儲存狀態的物件,裡面就可以存放許多自定義的屬性資料

日後會用到的資料,通常都會事先定義好屬性在 Store 中,即使是空字串、空陣列也沒關係,如果沒有事先定義好,之後就必須要用Vue.set的語法才能在 Store 中新增屬性資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const store = new Vuex.Store({
state:{
count:0,
userName:"",
list:[]
},
mutations:{
addCount(state){
state.count += 1;
},
setLoading(state){
//Store 中還沒有 loading 屬性,所以要用 Vue.set 去新增
Vue.set(state,'loading',false);
}
},
});

變更狀態的 Mutations

要改變 Store 中的屬性,唯一的方法是透過 Mutation,它的實作方法是在 callBack 函式中,把 state 作為參數傳進去,並在其中重新 assign 值給 state 的屬性。而 mutations 中的操作只能是「同步」操作,不能是非同步操作,如果要非同步操作只能在 actions 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

const store = new Vuex.Store({
state:{
count:0,
},
mutations:{
addCount(state){
state.count += 1;

//不得使用非同步操作,如:fetch
// fetch(url).then(()=>{
// state.count +=1;
// });
},
},
});

取得狀態的 Getters

Getter 其實就是 Store 中的 computed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

const store = new Vuex.Store({
state:{
todos:[],
},
getters:{
itemsNotDone(state){
return state.todos.filter(item=> !item.done).length;
},
//將 getters 拿進 getter 中用
itemsDone(state,getters){
return state.todos.length - getters.itemsNotDone;
},
//getter 也可以回傳出一個函式
itemWithID(state){
return ((id)=>{
return state.todos.filter(item=>item.id===id);
})
}
},
});

在組件中要引用 getters 的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


//<script>
import {mapStates, mapGetters} from 'vuex';
export default {
computed:{
...mapStates(['todos']),
...mapGetters(['itemNotDone','itemWithID']),
},
methods:{
test(){
this.itemWithID('123');
},
},
};

發出指令的 Actions

前面提過,Mutations 裡面的操作只能是同步的,但若要透過「非同步」 的方式改變 State 裡的資料,就必須使用 Actions 來做操作。可是 Action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

const store = new Vuex.Store({
state:{
todos:[],
},
mutations:{
setTodos(state,todos){
state.todos = todos;
}
},
actions:{
fetchTodos(context,payload){
fetch(url).
then(rs => rs.json()).
then(todos=>{
context.commit('setTodos',todos);
})
},

//context 是一個包含 store 中一切的東西,
//可是 context 比 store 還多了一些東西,
//所以 context 不等同於 store。
//因為我們只需要用到 context 中的 commit
//所以這裡經常會使用到重新解構的語法:
fetchTodos({commit},payload){
...
.then(todos=>{
commit('setTodos',todos);
})
...
}
}

});

在組件中要引用 actions 的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

//</script>
import { mapActions} from Vuex;
export default {
mounted(){
this.$store.dispatch('fetchTodos',{id:2});
},
//或是將傳入 dispatch 的參數包成一個物件的寫法
mounted(){
this.$store.dispatch({
type:'fetchTodos',//type 是固定的 key
id:2,
});
},
methods:{
...mapActions(['fetchTodos'])
}
};

mapAction語法

注意:如果是要帶參數就不可以使用此語法

使用mapAction語法,需在script下新增

1
import { mapGetters, mapActions } from 'vuex';

並在method裡的呼叫語法裡刪除既有的dispatch語法直接改成mapAction支援的格式
…mapActions([‘getProducts’]),