Vuex 是一個專為 Vue.js 開發的狀態管理套件,它將組件的狀態採用集中式的管理,並且保證這些狀態以一種可預測的方式發生變化(只能從 mutations 更動 state)。
如果我們遇到多個組件需共享狀態時,在不複雜的狀態中,通常我們可以使用 props、emits 來達成組件間彼此狀態的溝通(傳遞);不過如果組件間的層級較為複雜(多)或者兄弟組件的傳遞時,使用 props、emits 雖然可以解決問題,但組件間彼此耦合程度太高,組件間的狀態傳來傳去的,會造成後續調整、維護都較為困難。
這時我們可使用 Vuex 的概念將這樣的資料共享(抽取)出來,可想像有一個第三者幫我們管理好狀態部分,此時不論我們的組件相當多層(複雜),我們都可以很輕易取得同一筆資料,如果我們要修改這筆資料時,也可按照 Vuex 的規矩(路線)來做調整。
如上概念圖所示,我們在元件中透過 dispatch 去觸發 Vuex 的 Actions 告知“我們要做什麼”,然後 Actions 會把"我們要做什麼的資料"提交到 mutations ,而這個 mutations 才能去改變 state 的資料(也只能從這個入口進入)。
安裝起手
除了建置 vue cli 之外,我們可以透過 npm 或 yarn 在專案中加入 vuex
npm install vuex --save
yarn add vuex
接著我們在 src 目錄中創建 store/index.js 準備建置 vuex 相關程式:
// store/index.js
import Vue from "vue"; // 引入 Vue,為了 use vuex
import Vuex from "vuex"; // 引入 Vuex,我們要應用的插件
Vue.use(Vuex); // 在 Vue 中應用
const store = new Vuex.Store(); // 創建 store ,用於管理:state、actions、mutations
export default store; // export
// main.js
import Vue from "vue";
import store from "./store/index.js";
import Index from "./Index.vue";
Vue.config.productionTip = false;
new Vue({
store,
render: (h) => h(Index),
}).$mount("#app");
這樣創建完後,不論是 vue 的實體,或者到了元件的 vueComponent,都會有了 $store 這個屬性可以取用
定義與操作
這邊簡單操作一個增加計數的功能,快速了解 Vuex 各個環節。
首先我們先定義好 store 內的 state 資料,以及 actions、mutations 。
// store/index.js
import Vue from "vue"; // 引入 Vue,為了 use vuex
import Vuex from "vuex"; // 引入 Vuex,我們要應用的插件
Vue.use(Vuex); // 在 Vue 中應用
// 共享狀態定義
const state = {
count: 0,
};
// 創建 actions,包含響應組件"動作"的函式。
const actions = {
add(context, value) {
console.log(context); // 小型的 $store
console.log(value);
context.commit("ADD", value);
},
};
const mutations = {
ADD(state, value) {
state.count = value;
},
};
// 創建 store ,用於管理:state、actions、mutations
const store = new Vuex.Store({
state,
actions,
mutations,
});
export default store;
接著我們來到元件部分,當我們安裝 Vuex 並引入後,我們可以開始取得 $store 物件的內容,而取得 $store 的內容可以讓我們取到 vuex 中的資料,以及指派 vuex 要做什麼事情。
如下方程式碼所示:
我們可以透過 this.$store.state
去取得資料
也可以透過 this.$store.dispatch('add', 1)
表示我要指派 vuex actions 內的 add 函式做一些事情(也有帶入 1 這個 value)。
// index.vue
<template>
<div>
<h1>Index</h1>
<h2>{{ count }}</h2>
<button @click="add">增加按鈕</button>
</div>
</template>
<script>
export default {
name: "Index",
computed: {
count() {
// 用這樣的方式取到 vuex 中的資料。
return this.$store.state.count;
},
},
methods: {
add() {
// 指派 vuex 要做什麼事情
this.$store.dispatch('add', 1);
},
},
};
</script>
如下圖所式,
- 透過元件的事件去指派 (dispatch) 到 vuex actions 中的 add 函式
- 在 vuex actions 中,我們有兩個參數可以使用:
- context:此物件可以把它看做是一個小型的 $store,因此我們可以調用
context.commit
提交給 mutations ,或者操作context.state
、context.getters
等等。 - value:由元件中傳遞過來的資料,有時也不一定會有這個參數。(maybe 是非同步的資料)
- 在 vuex actions 中,最後我們操作了 context.commit("ADD", value),提交給 mutations 的 ADD 函式
- 在 vuex mutations 的 ADD 函式中,我們可以透過 state、value 兩個參數來變動 state 的資料。
非同步 Actions
這邊也簡單示意一個非同步的狀況,這邊的 api 會使用到 https://randomuser.me/api/ 。
情境示意:我們有一個透過 api 拿到的 user 資料需共享在許多的元件中,這時我們可以先定義 vuex 的部分。
- 在 state 中,我們定義好 user 為物件型態。
- 在 actions 中,我們定義好"動作" getuser 所需操作的非同步取資料,並且提交到 mutations。
- 在 mutations 中,我們可透過 actions 提交過來的資料來"變動" state。
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
const state = {
user: {},
};
const actions = {
getuser(context) {
axios.get("https://randomuser.me/api/").then((response) => {
const getData = response.data.results[0];
context.commit("GETUSER", getData);
});
},
};
const mutations = {
GETUSER(state, payload) {
state.user = payload;
console.log(state.user);
},
};
const store = new Vuex.Store({
state,
actions,
mutations,
});
export default store;
// index.vue
// ... 略
<script>
export default {
name: "Index",
// ... 略
mounted() {
this.$store.dispatch("getuser");
},
};
</script>
補充 getters(以上方非同步範例來說)
處理好 vuex 的資料部分後,我們可以在元件去取得資料:
- 直接在樣板中使用
this.$store.state.user.email
。(通常比較不會這樣寫) - 透過 computed 寫上
return this.$store.state.user.email
。(常見方式,也能做些資料的簡單處理) - 透過 vuex 的 getters ,我們就可以在元件中透過
this.$store.getters.xxx
取得我們要的資料。(常見方式,也能做些資料的簡單處理)
// index.vue
<template>
<div>
<h1>Index</h1>
<h2>使用者資料:</h2>
<p>EMAIL:{{ this.$store.state.user.email }}</p>
<p>EMAIL:{{ email }}</p>
<p>EMAIL:{{ email2 }}</p>
</div>
</template>
<script>
export default {
name: "Index",
computed: {
email() {
return this.$store.state.user.email;
},
email2() {
return this.$store.getters.email;
},
},
mounted() {
this.$store.dispatch("getuser");
},
};
</script>
// vuex index.js
// ... 略
const state = {
count: 0,
user: {},
};
// ... 略
const getters = {
email(state) {
return state.user.email;
},
};
const store = new Vuex.Store({
state,
actions,
mutations,
getters,
});
export default store;
結尾
有了 Vuex 作為資料控管後,資料主要會分為三種形式:
- 自己元件的 data >> 控制權大、關聯小
- props 從父層 >> 控制權中、關聯中
- Vuex store >> 控制權小、關聯大
以上是用 Vue CLI (vue2) 來做複習,與 vue3 僅有些許不同(例如:Vue.use...),不過概念是通用的,這邊主要是嘗試用自己的理解來做複習。
參考: