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

The introduction and difference between class component and function component in React

The introduction and difference between class component and function component in React

重新認識 JavaScript 之術(六角學院js直播課筆記)

重新認識 JavaScript 之術(六角學院js直播課筆記)

系統規劃-需求

系統規劃-需求


Comments