Vuex 集中式狀態管理


Posted by yaj55billy on 2021-11-22

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>

如下圖所式,

  1. 透過元件的事件去指派 (dispatch) 到 vuex actions 中的 add 函式
  2. 在 vuex actions 中,我們有兩個參數可以使用:
  • context:此物件可以把它看做是一個小型的 $store,因此我們可以調用 context.commit 提交給 mutations ,或者操作 context.statecontext.getters 等等。
  • value:由元件中傳遞過來的資料,有時也不一定會有這個參數。(maybe 是非同步的資料)
  1. 在 vuex actions 中,最後我們操作了 context.commit("ADD", value),提交給 mutations 的 ADD 函式
  2. 在 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 的資料部分後,我們可以在元件去取得資料:

  1. 直接在樣板中使用 this.$store.state.user.email 。(通常比較不會這樣寫)
  2. 透過 computed 寫上 return this.$store.state.user.email 。(常見方式,也能做些資料的簡單處理)
  3. 透過 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 作為資料控管後,資料主要會分為三種形式:

  1. 自己元件的 data >> 控制權大、關聯小
  2. props 從父層 >> 控制權中、關聯中
  3. Vuex store >> 控制權小、關聯大

以上是用 Vue CLI (vue2) 來做複習,與 vue3 僅有些許不同(例如:Vue.use...),不過概念是通用的,這邊主要是嘗試用自己的理解來做複習。

參考:

https://vuex.vuejs.org/zh/#什么是-状态管理模式


#Vue #vuex







Related Posts

AWS Solutions Architect - Associate (SAA) 學習計畫與備考心得: Module 5

AWS Solutions Architect - Associate (SAA) 學習計畫與備考心得: Module 5

FLIP 技巧總複習

FLIP 技巧總複習

[ES6 入門] 解構、展開、剩餘參數

[ES6 入門] 解構、展開、剩餘參數


Comments