Vuex-使用筆記

簡介

  • 主要用於管理 Vue 專案中共享的狀態
  • 提供了一個集中式存儲來管理所有元件的狀態
  • 來自不同元件的行為修改同一個狀態時,可以同時更新用到這狀態的所有地方

核心概念

state

  • 如同  data,是存放資料的地方

action

  • 如同  methods,用來處理非同步事件及取得遠端資料,但不負責處理資料內容的改變

getter

  • 如同  computed,在畫面渲染前先對資料進行運算及過濾等

mutation

  • 實際用於改變資料的內容

示意圖

note-img

如何使用 ( Vue3 + Vuex4 )

  1. 使用 Vue CLI 創建專案,並選擇配置 Vuex
  2. sotre資料夾中調整index.js檔案
import { createStore } from 'vuex'export default createStore({  state() {    return {      count: 1    }  },  getters: {},  mutations: {    increment(state, payload) {      state.count += payload    }  },  actions: {},  modules: {}})
  1. main.js檔案中引入 store
// main.jsimport { createApp } from 'vue'import { store } from './store'import App from './App.vue'const app = createApp(App)app.use(store)app.mount('#app')
  1. 在元件內 importuseStore 取得 store 實例
  • Vuex 的state通常會放到computed
<!-- ./components/CountItem.vue  --><template>  {{ count }}  <div>    <button type="button" @click="decreasement(6)">minus</button>    <button type="button" @click="increase(6)">plus</button>  </div></template><script>import { useStore } from 'vuex'import { computed } from 'vue'export default {  name: 'CountItem',  setup() {    const store = useStore()    const count = computed(() => store.state.count)    const increase = (num) => {      store.commit('increment', num)    }    const decreasement = (num) => {      store.commit('decreasement', num)    }    return {      count,      increase,      decreasement    }  }}</script>

運作流程

在同步操作和簡單邏輯時

mutations只能處理同步邏輯

  • 通過  store.state  來獲取狀態對象,並通過  store.commit  方法觸發狀態變更
  • 可以跳過 actions,直接使用 mutations 改變狀態

在進行非同步操作時

非同步邏輯,必須使用actions

  • 元件透過  dispatch  方法來觸發  actions  事件
  • actions  執行 AJAX,取得遠端的資料
  • 接著  actions  使用  commit  方式去呼叫  mutations
  • 透過  mutations  去改變資料狀態(state
  • 最後把資料狀態回應給元件去渲染畫面
// ./store/index.jsimport { createStore } from 'vuex'export default createStore({  state() {    return {      todo: null    }  },  mutations: {    SET_DATA(state, newData) {      state.todo = newData // 更新狀態    }  },  actions: {    async fetchData({ commit }) {      try {        const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')        const data = await response.json()        commit('SET_DATA', data) // 使用commit呼叫mutation      } catch (error) {        console.error('Fetch data error:', error)      }    }  }})
<!-- TodoList.vue --><template>  <div v-if="todo">    {{ todo }}  </div>  <button @click="getTodo">取得Todo</button></template><script>import { computed } from 'vue'import { useStore } from 'vuex'export default {  setup() {    const store = useStore()    const todo = computed(() => store.state.todo)    const getTodo = async () => {      await store.dispatch('fetchData')    }    return {      todo,      getTodo    }  }}</script>

modules 模組化

  • 當專案規模變大時,所有的狀態都放在index.js
  • 可以利用 modules 依照功能區分,以便維護管理

操作步驟:

  1. 在 store 資料夾,新增modules
  2. 新增user.js存放使用者相關的資料
  3. 設定namespaced: true,替模組加上命名,會自動根據路徑調整命名
// user.jsexport const user = {  namespaced: true,  state: () => ({    name: 'Ben'  }),  mutations: {    SET_NAME(state, name) {      state.name = name    }  },  actions: {    updateName({ commit }, name) {      commit('SET_NAME', name)    }  },  getters: {    userName: (state) => state.name  }}
  • 在 Vue 元件內,可以這樣使用
<!-- User.vue --><template>  <div>    <p>User: {{ userName }}</p>    <button type="button" @click="changeName('Pan')">change</button>  </div></template><script>import { useStore } from 'vuex'import { computed } from 'vue'export default {  name: 'App',  setup() {    const store = useStore()    const userName = computed(() => store.getters['user/userName'])    const changeName = (name) => {      store.dispatch('user/updateName', name)    }    return {      userName,      changeName    }  }}</script>

map 輔助函式(Options API)

  • 在 Options API 中,隨著專案規模擴大,例如要把 Vuex 當中的每個 state,都使用computed宣告較麻煩
  • 提供 map 輔助函式,提供更簡潔的寫法
  1. mapState
  2. mapGetters
  3. mapMutations
  4. mapActions
  • 以下使用 Vue2 的 Options API 寫法
// ./store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({  state: {    count: 0,    isLoggedIn: false,    userName: 'Guest'  },  getters: {    doubleCount: (state) => state.count * 2  },  mutations: {    increment(state) {      state.count++    }  },  actions: {    incrementAsync({ commit }) {      setTimeout(() => {        commit('increment')      }, 1000)    }  }})
<template>  <div>    <p>Count: {{ count }}</p>    <p>User Name: {{ userName }}</p>    <p>Logged In: {{ isLoggedIn }}</p>  </div></template><script>import { mapState } from 'vuex'export default {  computed: {    // 使用 mapState 的陣列形式    ...mapState([      'count', // 映射 this.count 為 store.state.count      'isLoggedIn', // 映射 this.isLoggedIn 為 store.state.isLoggedIn      'userName' // 映射 this.userName 為 store.state.userName    ])    // 其他computed屬性  }  // 其他methods}</script>

檔案結構

├── index.html├── main.js├── api   └── ... # 抽取出API請求├── components   ├── App.vue   └── ...└── store    ├── index.js          # 彙整所有的store,並導出    ├── actions.js        # 當專案規模很大時,可以把actions單獨拆出來    ├── mutations.js      # 當專案規模很大時,可以把mutations單獨拆出來    └── modules        ├── cart.js       # 購物車模塊        └── products.js   # 產品模塊

參考資料

# Vue # Vuex