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.js
import { 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.js
import { 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.js
export 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.js
import 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