# [Vue] Vuex 3 ###### tags: `Vue.js` :::warning 以下資料適用於 Vuex 3 & Vue 2 如果是用 Vuex 4 & Vue 3,要看[這邊的文件](https://next.vuex.vuejs.org/ "Vuex") 但 Vue 3 建議使用 [Pinia](https://pinia.vuejs.org/) ::: * Vue 應用程式的狀態管理 * 單向資料流 * 將資料、方法放到 Vuex 內管理,所有元件都可呼叫方法,並維持資料雙向綁定的特性 ## Vuex 流程 ![image alt](https://vuex.vuejs.org/vuex.png "vuex") 圖片來源:[Vuex 是什么?](https://vuex.vuejs.org/zh/ "Vuex 是什么? | Vuex") Vue Component 透過 Dispatch 觸發 Actions,Actions 會去取得遠方資料或處理非同步行為,再使用 Commit 方式呼叫 Mutations,透過 Mutations 改變 State 資料狀態,最後將資料回應給 Vue Component Render 到畫面 | 核心概念 | 說明 | | ---- | --- | | State | 如同 data,儲存資料狀態的地方<br>屬於模組區域變數 | | Actions | 如同 methods,進行非同步的行為及取得資料,不會改變 state 資料狀態<br>屬於全域變數 | | Getters | 如同 computed,資料呈現的方式<br>屬於全域變數 | | Mutations | 改變資料內容的方法,不會執行非同步的行為<br>屬於全域變數 | ## 使用範例 ### /store/index.js ```javascript= import Vue from 'Vue'; import Vuex form 'Vuex'; import axios from 'axios'; import productsModule from '.products.js'; Vue.use(Vuex); export default new Vuex.store({ strict: process.env.NODE_ENV !== 'production', // 嚴謹模式,若在 mutations 以外改變資料會提示,但不要在正式環境使用 state: { isLoading: true, products: [], }, actions: { // payload 只能接受一個參數,若要一次傳多個,可以用物件形式傳遞,例:{ id, qty } updateLoading(context, payload) { context.commit('LOADING', payload); }, getProducts(context) { const url = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/products` context.dispatch('updateLoading', true); axios.get(url).then((resp) => { context.commit('PRODUCTS', resp.data.products); context.dispatch('updateLoading', false); }); }, }, mutations: { LOADING(state, payload) { state.isLoading = payload; }, PRODUCTS(state, payload) { state.products = payload; }, }, getters: { products(state) { return state.products; }, }, // 可以依功能將相關的程式碼獨立出來,維護上較方便 modules: { productsModule, }, }); ``` ### .vue 檔的 <script></script> ```javascript= import { mapGetters, mapActions } from 'vuex'; export default { created() { this.getProducts(); }, methods: { showLoading() { // Dispatching Actions 方法一 this.$store.dispatch('updateLoading', true); }, // Dispatching Actions 方法二 ...mapActions(['getProducts']), }, computed: { ...mapGetters(['products']), }, }; ``` ## namespaced: true Actions、Getters、Mutations 屬於全域變數,若在不同 module 內用到相同的命名會有衝突,可以加上 `namespaced: true`,這樣就會變成模組區域變數,但在使用上就要改成下方寫法 ### 使用範例 #### /store/products.js ```javascript= import axios from 'axios'; export default { namespaced: true, state: { products: [], }, actions: { getProducts(context) { const url = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/products` context.dispatch('updateLoading', true, { root: true }); // 不在此 module 內,是 Global 的,要加上 { root: true } axios.get(url).then((resp) => { context.commit('PRODUCTS', resp.data.products); context.dispatch('updateLoading', false, { root: true }); }); }, }, mutations: { PRODUCTS(state, payload) { state.products = payload; }, }, getters: { products(state) { return state.products; }, }, }); ``` ### .vue 檔的 <script></script> ```javascript= import { mapGetters, mapActions } from 'vuex'; export default { created() { this.getProducts(); }, methods: { ...mapActions('productsModule', ['getProducts']), }, computed: { ...mapGetters('productsModule', ['products']), }, }; ``` ### [官方的範例](https://vuex.vuejs.org/zh/guide/modules.html#%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4 "Module 命名空间") ```javascript= // 模块内容(module assets) const store = new Vuex.Store({ modules: { account: { namespaced: true, // 模块内容(module assets) state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响 getters: { isAdmin () { ... } // -> getters['account/isAdmin'] }, actions: { login () { ... } // -> dispatch('account/login') }, mutations: { login () { ... } // -> commit('account/login') }, // 嵌套模块(nested modules) modules: { // 继承父模块的命名空间(inherits the namespace from parent module) myPage: { state: () => ({ ... }), getters: { profile () { ... } // -> getters['account/profile'] } }, // 进一步嵌套命名空间(further nest the namespace) posts: { namespaced: true, state: () => ({ ... }), getters: { popular () { ... } // -> getters['account/posts/popular'] } } } } } }) ``` ## 注意事項 * 不要直接取用 state 的資料,透過 gettets 可以寫處理資料的邏輯,這樣 component 只要單純取出資料即可 * 不建議直接在 `<template>` 內寫 `store.getters.xxx`,透過 `computed()` 取得較好 * 統一整個架構的資料流向:在 component 使用 `dispatch` 呼叫 `actions` 觸發 `mutations` 改變資料,不要直接用 `store.commit` 觸發 `mutations` ## 參考資料 * [Vuex](https://v3.vuex.vuejs.org/ "Vuex") * [Vue 出一個電商網站](https://www.udemy.com/course/vue-hexschool/ "Vue 出一個電商網站") --- :::info 建立日期:2021-01-02 更新日期:2023-08-11 :::