# [Vue] Vuex 筆記 ###### tags: `Vue` `Vuex` `前端筆記` ![](https://i.imgur.com/TstViCH.png) (ref: [Vue - The Complete Guide (incl. Router & Composition API](https://www.udemy.com/course/vuejs-2-the-complete-guide/)) ## Vuex 是什麼? Vuex 根據官方文件的說法是一個「狀態(state)管理庫」,Vuex 提供的方法,可以供使用者把資料(state)存入 Vuex 提供的 store 中,存入的 store 的 state 就好比像是網頁中 window 的全域物件,每個 component 都可以存取。 ## Vuex 幫助了什麼? 專案越長越大,如果單純地使用 props 由上至下的傳遞資料在跨層級傳遞的時候就會出現問題(props -> props -> props 的連接),雖然出現了 `provide / inject` 提供使用者跨層級的傳遞,但是還是會出現根(root)資料(root)到底要放哪裡?就算是放在 component 中最後該 component 會變成超級長(因為有自己 component 的 state, template, style 等等...),造成管理不順。 好在 Vuex 被發明出來解決這個問題,Vuex 提供了一個統一的儲存點,讓開發者可以把共用的根(root)資料(state)存入至 Vuex 的 store 內,開發者只要遵照 Vuex 規定的單向資料流(one-way data flow),便可以有效率且清楚地管控資料。 > 另外,由於資料流是單向的,所以 Vue.js 的響應式更新才能落實到每個元件上,當資料被更新,Vue / Vuex 馬上就會知道,不需要透過迴圈一個一個去檢查哪個元件的狀態是否已經更新完成。 > *ref. 008 天絕對看不完的 Vue.js 3 指南,p. 248* ![](https://i.imgur.com/DGoirzy.png) (Vuex 的 one-way data flow 示意圖) ## 基本安裝 / 使用 ### 1. 透過 Vue CLI 建立專案時就安裝 ![](https://i.imgur.com/FpJLIiV.png) 那麼安裝結束後 Vue CLI 會貼心地幫我們建立檔案(src/store/index.js) ```javascript= // src/store/index.js // 引入 createStore -> 用來建立 store 的實例 import { createStore } from 'vuex'; // 建立 store 的實例 export default createStore({ state: { }, mutations: { }, getters: { }, actions: { } }); ``` ### 2. 也可以自己手動安裝 `$ npm install vuex@next --save` 完成後手動新增檔案(src/store/index.js) ```javascript= // src/store/index.js // 引入 createStore -> 用來建立 store 的實例 import { createStore } from 'vuex'; // 建立 store 的實例 const store = createStore({ state () { return { } }, mutations: { }, getters: { }, actions: { } }); export default store; ``` 不管用哪個方法最後都要到接口(src/main.js)中讓 Vue App 使用 store ```javascript= // src/main.js import { createApp } from 'vue' const app = createApp({ /* your root component */ }) // Install the store instance as a plugin app.use(store) ``` ==為了方便管理,通常會把 Vuex 的實例放在獨立的 folder 中,再引入到 Vue App 的接口使用。== ## state Vuex 中的 state 就像是 component 中 data 方法所回傳的物件,是 Vuex 管理資料的地方。但是不同於 component 中的 data 方法, Vuex 中的 state 在 Vue app 類似於網頁中 window 的全域物件,亦即整個 Vue app 都可以取得 state。 基本的使用範例: ```javascript= // src/store/index.js import { createStore } from 'vuex'; const store = createStore({ state () { return { test: 123, }; } }); export default store; ``` ```javascript= // src/views/MyComponent.vue <template> <p>{{ getData }}</p> </template> <script> export default { name: MyComponent, // 透過 computed 抓取 store state 顯示在 template 上 computed: { getData () { // this.$store.state.objectName -> 取用 state 中的 pair return this.$store.state.test; } } } </script> ``` ### 如果資料很多的話可以用 helper 用 helper 在需要取用很多資料的情況中能夠有效率地減少重複的程式碼: ```javascript= // src/store/index.js import { createStore } from 'vuex'; const store = createStore({ state () { return { test: 123, test2: 222, test3: 333, } }, }); export default store; ``` 當要取用多個 state 的時候就會看起來很醜,因為重複很多次 `this.$store.state.xxx`: ```javascript= // src/views/MyComponent.vue <template> <p>{{ getData }}</p> <p>{{ getData2 }}</p> <p>{{ getData3 }}</p> </template> <script> export default { name: MyComponent, // 透過 computed 抓取 store state 顯示在 template 上 computed: { // 沒使用 helper 重複了許多次 this.$store.state.xxx getData1 () { return this.$store.state.test; }, getData2 () { return this.$store.state.test2; }, getData3 () { return this.$store.state.test3; } } } </script> ``` 使用 `mapState` 幫助開發者減少重複性的程式碼: ```javascript= // src/views/MyComponent.vue <template> <p>{{ test }}</p> <p>{{ test2 }}</p> <p>{{ test3 }}</p> <!-- 以物件的方式 --> <p>{{ getData1 }}</p> <p>{{ getData2 }}</p> <p>{{ getData3 }}</p> </template> <script> // 在 component 先引用 helper import { mapState } from 'vuex'; export default { name: MyComponent, // 透過 computed 抓取 store state 顯示在 template 上 computed: { // 在 computed 中叫用 mapState // 記得要用展開運算子(...),避免 mapState 把其他 computed 的方法吃掉 // 1. 用陣列的方式 ...mapState(['test', 'test2', 'test3']), // 等價於 this.$store.state.test, this.$store.state.test2... // 用陣列的方式就要以 keyName 為值,並於 template 寫入期 keyName } // 2. 也可以透過物件的方式,重新再 template 給新的名字 // 既然給了新的名字,就要在 template 用新的名字叫用 ...mapState[{ getData1: 'test', getData2: 'test2', getData3: 'test3' }] // 3. 也可以用函式的方式 ...mapState({ getData1Fun: state => state.test, getData2Fun: state => state.test2, getData3Fun: state => state.test3, }) } </script> ``` 透過 `mapState` 可以省略很多相同的程式碼,也提供使用者以不同的手段叫用 Vuex state 中的資料。 ## getters ==getters 就像是 component 中的 computed。== 被叫用的時機點與 computed 大同小異。 1. component 第一次載入時 2. 函式內部監控的 state 有改變時(一般 return state + 自己想要改的樣子) ### getters 的 parameters getters 可以放兩個 parameters 叫用: 1. state -> 讀取 state,但在 module(模組化)只會讀到自己模組內的 state (locally) 2. getters -> 讀取其他的 getters(會得到該 getters 要回傳的東西),但在 module(模組化)只會得到自己模組內的 getters(locally) #### 使用範例 1. 只下 state ```javascript= // src/store/index.js // ref. https://vuex.vuejs.org/guide/getters.html#property-style-access const store = createStore({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos (state) { return state.todos.filter(todo => todo.done) } // [{id: 1, text: '...', done: true}] } }) ``` ```javascript= // src/views/MyComponent.vue <template> <p>{{ getDoneTodos }}</p> <!-- 會看到 [{id: 1, text" '...', done: true}] 顯示在網頁中 --> </template> <script> export default { name: 'MyComponent', computed: { getDoneTodos () { return this.$store.getters.doneTodos; } } } </script> ``` 2. 下第二個 parameters getters 取得其他 getters 回傳的值 ```javascript= // src/store/index.js // ref. https://vuex.vuejs.org/guide/getters.html#property-style-access const store = createStore({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos (state) { return state.todos.filter(todo => todo.done) }, // [{id: 1, text: '...', done: true}] doneTodosCount (state, getters) { return getters.doneTodos.length }, } }) ``` ```javascript= // src/views/MyComponent.vue <template> <p>{{ getDoneTodosCount }}</p> <!-- 1 --> </template> <script> export default { name: 'MyComponent', computed: { getDoneTodosCount () { return this.$store.getters.doneTodosCount; } } } </script> ``` ### getters 也可以回傳另一個函式,藉由 scope(範疇)的原理增加使用上的彈性 ```javascript= // src/store/index.js // ref. https://vuex.vuejs.org/guide/getters.html#property-style-access const store = createStore({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { // ... getTodoItems: (state) => { return state.todos.map((todo) => todo) }, getTodoById (state) { return function (id) { return state.todos.find((todo) => todo.id === id) } } // 寫成箭頭函式更簡潔 getTodoById: (state) => (id) => { return state.todos.find((todo) => todo.id === id) } // 回傳一個函式,藉由範疇的觀念:函式內部可以拿外部的東西,所以回傳的函式有辦法讀取到外部函式的 state parameter -> 代表 vuex 的 state } }) ``` ```javascript= // src/views/MyComponent.vue <template> <p v-for="(item) in getTodoItems" :key="item.id" @click="findTodoById(item.id)"> {{ item.item }} </p> <p v-if="currentActiveItem">{{ currentActiveItem }}</p> </template> <script> import { mapGetters } from 'vuex'; export default { name: 'MyComponent', data () { return { currentActiveItem: '', } }, methods: { findTodoById (id) { const temp = this.getTodoById(id) this.currentActiveItem = temp } }, computed: { // mapGetters 對待 getters 回傳函式的道理就像是一般 import 函式一樣,import 時不需寫 parameter,因為這樣子 = 叫用函式 // 叫用時自己再寫 () 叫用即可 ...mapGetters(['getTodoItems', 'getTodoById']) } } </script> ``` ![](https://i.imgur.com/hfNW21f.png) (透過 getters 取得 state 的資料) ![](https://i.imgur.com/51nNpQ5.png) (點擊事件後透過 getters 取得 state 資料再更改 component 內的資料變更畫面) ### `mapGetters` getters 的 helper getters 也有 helper 可以用,幫助開發者更簡單地叫用不同的 getters: ```javascript= // scr/components/MyComponent.vue // 一樣先 import helper 函式 import { mapGetters } from 'vuex' export default { // ... computed: { // mix the getters into computed with object spread operator ...mapGetters([ 'doneTodosCount', 'anotherGetter', // ... ]) // 等價於 this.$store.getters.doneTodoCount ... } } ``` 也可以用物件的方式在 component 內重新命名: ```javascript= ...mapGetters({ // map `this.doneCount` to `this.$store.getters.doneTodosCount` doneCount: 'doneTodosCount' // 在 component 內用 doneCount 直接叫用 this.$store.getters.doneTodoCount }) ``` 如果 getters return 函式時要注意的小地方: 1. 在 component 叫用要 + this -> `this.myGetters()`,且可以自行包裝在 component 的 methods 中使用 2. 就像是一般 `import` 有 parameters 的函式一樣,`import` 時不需要加括號 `()`,這樣子會變成叫用該函式 ## mutation ==唯一能夠更改 state 資料的手段。== 叫用 mutation 時可以下兩個 parameters(state, payload)。 ### 1. state 顧名思義就是讀取 state,但是在 module(模組化)時只能讀取 module state(locally) ### 2. payload payload 可以接收外部傳來的值,可以想像成事件要接新的值那樣子。 #### 如果要傳很多值,但是 mutation 只有一個 payload 可以用要怎麼辦? 可以使用物件搭配物件解構,就可以突破只有一個 payload 可以傳值的困擾了。 ```javascript= // ref. https://vuex.vuejs.org/guide/mutations.html#commit-with-payload // ... mutations: { increment (state, payload) { state.count += payload.amount } } // 解構也 OK mutations: { increment (state, { amount }) { state.count += amount } } ``` ```javascript= store.commit('increment', { amount: 10 }) ``` ### 需要透過 `commit('mutationName')` 叫用 mutation mutation 就像是事件註冊一樣,不能直接叫用,需要透過 `commit('mutationName')` 叫用。(mutationName 要用字串的形式)。 ```javascript= // src/components/MyComponent.vue <template> // ... </template> <script> export default { // ... methods: { handleClick () { this.$store.commit('myMutation', { myData: 123 }) } } } </script> ``` ### mutations 只能負責處理同步請求 如果需要處理非同步請求的話需要用 actions,而不是用 mutations。 ### `mapMutations` mutations 的 helper 如果有很多 mutations 的話,vuex 也有提供 `mapMutations` helper 幫助開發者省略重複的程式碼。 使用後就可以像一般的 methods 一樣用 `this+mutaionName` 叫用 mutations: ```javascript= // ref. https://vuex.vuejs.org/guide/mutations.html#committing-mutations-in-components // src/components/MyComponent.vue // import helper import { mapMutations } from 'vuex' export default { // ... methods: { // 在 methods 內叫用 mapMutations ...mapMutations([ 'increment', // map `this.increment()` to `this.$store.commit('increment')` // `mapMutations` also supports payloads: 'incrementBy' // map `this.incrementBy(amount)` to `this.$store.commit('incrementBy', amount)` ]), // 也可以在 component 內改名,並以新名叫用 mutation ...mapMutations({ add: 'increment' // map `this.add()` to `this.$store.commit('increment')` }) } } ``` ### 管理 mutations 名稱的好方法 —— Mutation Types 在大型專案中一定會有超多的 mutations,vuex 官方建議用另一個獨立的 Js file 統一管理 mutations 的名稱: 1. 注意名稱接為大寫,單詞之間以底線隔開 2. `mutationTypes.js` 就只是一個管理名稱的檔案 3. 需要用到該名稱就要 `import`,並用 `[MUTATION_NAME](state){...}` 宣告該 mutation ```javascript= // src/storc/mutationTypes.js export const APP_LOADING = 'APP_LOADING' export const APP_ALERT_DATA = 'APP_ALERT_DATA' // ... ``` ```javascript= // src/store/index.js import { createStore } from 'vuex' import { APP_LOADINGOME } from './mutation-types' const store = createStore({ state: { ... }, mutations: { // we can use the ES2015 computed property name feature // to use a constant as the function name [APP_LOADING] (state) { // mutate state } } }) ``` ## actions -> Vuex 處理非同步的手段 1. 如果有非同步的需求請用 actions(比方來說打 API 資料) 2. actions 如果想要更改 state,需要用 action 叫用 mutation 更改 state actions 本身提供兩個 parameters 供開發者叫用 actions 時使用: ### 1. context >context 是一個與 vuex 實體相同的物件,雖然它們具有相同的方法與屬性,但 context 並不是 store 本身。 >*ref. 008 天絕對看不完的 Vue.js 3 指南 p.263* context 內部有 { commit, dispatch, getters, rootGetters, rootState, state } 屬性。 #### commit 透過 `commit('mutationName')` 開發者可以在 actions 內叫用 mutation。 #### dispatch `dispatch('actionName')` 叫用其他 actions。 #### getters 可以取得 module local 的 getters。 #### state 可以取得 module local 的 state。 #### rootGetters 可以取得根(root)的 getters。 #### rootState 可以取得根(root)的 state。 ### 2. payload 這部分就和 mutation 的 payload 一樣,是用來接收外部的值(也可用物件解構得到多個值)。 ### 使用範例 ```javascript= // ref. https://vuex.vuejs.org/guide/actions.html // src/store/index.js const store = createStore({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { // action 可以叫用 mutation -> 透過 commit context.commit('increment') } } }) ``` ```javascript= // ref. https://vuex.vuejs.org/guide/actions.html // src/components/MyComponents.vue <template> // ... <button @click="handleClick">Click</button> </template> <script> export default { // ... methods: { handleClick () { // 在 component 中叫用 action // -> this.$store.dispatch('actionName', payload) this.$store.dispatch('increment') } } } </script> ``` ### 簡單用非同步: #### 1. setTimeout 的基礎使用 ```javascript= // src/store/index.js // ... actions: { actionA ({ commit }) { console.log('in actionA') setTimeout(() => { commit('mutationA') }, 2000); } }, mutations: { mutationA () { console.log('mutation A'); } } ``` ```javascript= // src/components/MyComponent.vue <template> // ... <button @click="playAction">Test</button> <template> <script> // ... methods: { playAction () { this.$store.dispatch('actionA') } } </script> ``` 2.5 秒後才執行 mutationA ![](https://i.imgur.com/JWFvo9n.png) #### 2. 使用 Promise ```javascript= // src/store/index.js // ... actions: { actionA ({ commit }) { // 2 console.log('in actionA') return new Promise ((resolve) => { setTimeout(() => { commit('mutationA') resolve() }, 2000) }) }, actionB ({ commit, dispatch }) { // 1 console.log('actionB') return dispatch('actionA').then(() => { commit('mutationB') }) } }, mutations: { mutationA () { // 3 console.log('mutation A'); }, mutationB () { // 4 console.log('mutationB') }, } ``` ```javascript= // src/components/MyComponent.vue <template> // ... <button @click="playAction">Test</button> <template> <script> // ... methods: { playAction () { this.$store.dispatch('actionB') } } </script> ``` 等到 Promise resolve 後才會執行 `.than(() => commit('muationB')` ![](https://i.imgur.com/2g3tiCs.png) #### 3. async / await ```javascript= // src/store/index.js // ... actions: { actionA ({ commit }) { console.log('in actionA') return new Promise ((resolve) => { setTimeout(() => { commit('mutationA') resolve() }, 2000) }) }, actionB ({ commit }) { console.log('actionB') commit('mutationB') } }, mutations: { mutationA () { console.log('mutation A'); }, mutationB () { console.log('mutationB') }, } ``` ```javascript= // src/components/MyComponent.vue <template> // ... <button @click="playAction">Test</button> <template> <script> // ... methods: { async playAction () { await this.$store.dispatch('actionA'); this.$store.dispatch('actionB') } } </script> ``` asnyc / await 語法糖讓非同步看起來像是同步執行,async function 會確保內部的 await 執行完再繼續執行內部函式的任務 ![](https://i.imgur.com/8iufi4c.png) ### `mapActions` actions 的 helper 能夠省略重複程式碼的 `mapActions`: ```javascript= // ref. https://vuex.vuejs.org/guide/actions.html#dispatching-actions-in-components // src/components/MyComponent.vue import { mapActions } from 'vuex' export default { // ... methods: { ...mapActions([ 'increment', // map `this.increment()` to `this.$store.dispatch('increment')` // `mapActions` also supports payloads: 'incrementBy' // map `this.incrementBy(amount)` to `this.$store.dispatch('incrementBy', amount)` ]), ...mapActions({ add: 'increment' // map `this.add()` to `this.$store.dispatch('increment')` }) } } ``` ## module 模組化 隨著專案的成長,如果單用一個 JS file 管理全部 component 的 state 會顯得該檔案很難管(因為一定會行數爆表)。好在 vuex 本身也有提供 module 模組化的方式協助開發者切分管理 state 的檔案。 簡單的區分模組範例: ```javascript= const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } } const store = createStore({ modules: { a: moduleA, b: moduleB } }) ``` 簡單的取用方式: ```javascript= // in component this.$store.state.a.stateKeyName // -> `moduleA`'s state this.$store.state.b.stateKeyName // -> `moduleB`'s state // 因為 getters / actions / mutations 預設是 under global namespace this.$store.getters('getterOfModuleA') // -> `moduleA`'s getters this.$store.dispatch('ActionOfModuleB') // -> `moduleB`'s actions ``` #### 拆分模組化的方法有哪些呢? 其實還是要看團隊風格為主,目前接觸到的有: 1. 先區分 module ,把每個屬性(actions, mutations, getters)都丟成一個 file 最後再 import 到 index.js 2. 也有區分 moduel 後只有照 module 分 file 把屬性寫在一起的。比方來說 cart.js 裡面就有該 module 的全部屬性(actions, muations, getters) ==但重點就是建議要創建資料夾 `src/store` 當作存放 vuex store 的位置。== 以目前公司專案的架構為例 ```javascript= project └───src │ │ App.vue // 主頁面 │ │ main.js // 主入口 │ │ | |___ router // 路由 │ | |___index.js // 所有路由 │ │ │ |___assets // 需webpack處理資源 │ │ │ |___static // 不需webpack處理資源 │ │ │ |___style // scss資源 │ | |___variables.js // scss變數 │ │ │ |___lang // i18n多國語系 │ | |___index.js // i18設定 │ | |___zh-TW.json // 繁體中文 │ | |___en-US.json // 英文 | | | |___components // 共用組件 | | | |___plugins // 第三方插件 | | |___themes.js // 更改vuetify的樣式 | | |___vuetify.js // vuetify 設定相關 | | | |___server // api相關 | | |___index.js // 所有api | | |___http.js // axios 二次封裝 | | | |___store // vuex | | |___index.js //主store | | |___ modules | | |___|___moduleName.js // 各種不同 module 的 store | | | |___utils // 共用工具 | | |___index.js// 共用 function | | | |___views // 頁面 | | |___Admin // 後台頁面 | └───public // 公用文件 │ |___favicon.ico │ |___index.html │ .gitignore // 不用上傳git設定 │ vue.config.js // vue-cli 設定 │ babel.config.js// babel設定 │ .eslintrc.js // eslint設定 │ package-lock.json // npm dependencies 版本lock │ package.json // npm │ README.md // 專案說明 ``` ### 在模組中 state 代表模組內部的 state(locally) module 內部的 mutations, getters 中的 state 只會抓到 module 自己的 state,不會往外抓根(root)的 stata。 同理,action 中的 `context.state` 只會抓到 module 自己的 state,如果想要抓 root state 就要用 `context.rootState`。 ```javascript= // src/store/module/moduleA.js const moduleA = { state: () => ({ count: 0 }), mutations: { increment (state) { // `state` is the local module state state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } }, actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } } } ``` ### 將 module 的 getters / actions/ mutations 區分開來的 `namespaced: true` 因為 vuex 預設 actions / mutations / getters 是 under global namespace 的,如果 module 內有相同名稱的 mutations / actions 在預設狀態的話會一起被叫用,而同時叫用兩個相同名稱但位於不同 module 的 getters 則是會報錯。好在 vuex 提供了一組屬性,`namespaced: true`,透過新增這個屬性 vuex 會自動在 module 的 actions / mutations / getters / state 前加上 module namespace 的 `prefix`,如果要叫用該 module 的 actions / mutations / getters / state 時必須加上對應的 module namespace。 ```javascript= const moduleA = { namespced: true, state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { namespced: true, state: () => ({ ... }), mutations: { ... }, actions: { ... } } const store = createStore({ modules: { a: moduleA, b: moduleB } }) ``` 取用方式: ```javascript= this.$store.commit('a/mutatuinName') this.$store.dispatch('b/actionName') this.$store.getters['a/getters'] this.$store.state.a.stateKeyNem // module state is already nested and not affected by namespace option ``` 巢狀的 module 也支援: ```javascript= const store = createStore({ modules: { account: { namespaced: true, // module assets state: () => ({ ... }), // module state is already nested and not affected by namespace option getters: { isAdmin () { ... } // -> getters['account/isAdmin'] }, actions: { login () { ... } // -> dispatch('account/login') }, mutations: { login () { ... } // -> commit('account/login') }, // nested modules modules: { // inherits the namespace from parent module // 從 parent 繼承同樣的 namespace myPage: { state: () => ({ ... }), getters: { profile () { ... } // -> getters['account/profile'] } }, // further nest the namespace posts: { namespaced: true, state: () => ({ ... }), getters: { popular () { ... } // -> getters['account/posts/popular'] } } } } } }) ``` ### `namespaced: true` 取得 module 之外之方法的手段 1. getters 可以取得 rootGetters 及 rootState,使用第三、四個 parameters 即可。module action 也可以透過 context 中的屬性取得 rootState 及 rootGettersa 2. 如果想要在 module actions 內叫用 root 的 mutations / actions,要在 `commit` 及 `dispatch` 中下第三個參數 `{ root: true }` 要不然會因為屬性 `namespace: true` 影響,只會找 module 內部有的,不會找 root ```javascript= modules: { foo: { namespaced: true, getters: { // `getters` is localized to this module's getters // you can use rootGetters via 4th argument of getters // 因為 namespaced 的幫助,所以 getters param. 會找 module 內的 getters someGetter (state, getters, rootState, rootGetters) { getters.someOtherGetter // -> 'foo/someOtherGetter' rootGetters.someOtherGetter // -> 'someOtherGetter' (找 rootGetters) rootGetters['bar/someOtherGetter'] // -> 'bar/someOtherGetter' (從 rootGetters 再進入 bar module) }, someOtherGetter: state => { ... } }, actions: { // dispatch and commit are also localized for this module // they will accept `root` option for the root dispatch/commit // 因為 namespaced 的幫助,所以 dispatch, commit param. 會找 module 內的 actions / mutations someAction ({ dispatch, commit, getters, rootGetters }) { getters.someGetter // -> 'foo/someGetter' rootGetters.someGetter // -> 'someGetter' rootGetters['bar/someGetter'] // -> 'bar/someGetter' dispatch('someOtherAction') // -> 'foo/someOtherAction' // 加入第三個 param. { root: true } // -> 會從 root 找 action(someOtherAction) dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction' commit('someMutation') // -> 'foo/someMutation' // -> 會從 root 找 mutation(someMutation) commit('someMutation', null, { root: true }) // -> 'someMutation' }, someOtherAction (ctx, payload) { ... } } } } ``` ### helpers 配上 `namespaced: true` #### `mapState` ```javascript= // ref. https://jigsawye.com/2017/08/30/vuex-module-namespacing computed: { ...mapState({ a: state => state.some.nested.module.a, // this.a b: state => state.some.nested.module.b // this.b }) }, // to computed: { ...mapState('some/nested/module', { a: state => state.a, // this.a b: state => state.b // this.b }), // or ...mapState('some/nested/module', [ 'a', // this.a 'b' // this.b ]) // alias ...mapActions('some/nested/module', { dataA: 'a', // this.dataA dataB: 'b' // this.dataB }) }, ``` #### `mapActions` ```javascript= // ref. https://jigsawye.com/2017/08/30/vuex-module-namespacing methods: { ...mapActions([ 'some/nested/module/foo', // this.foo() 'some/nested/module/bar' // this.bar() ]) } // to methods: { ...mapActions('some/nested/module', [ 'foo', // this.foo() 'bar' // this.bar() ]), // alias ...mapActions('some/nested/module', { fooA: 'foo', // this.fooA(); barA: 'bar' // this.barA() }) } ``` #### `mapGetters` ```javascript= computed: { ...mapGetters([ 'some/nested/module/someGetter', // -> this['some/nested/module/someGetter'] 'some/nested/module/someOtherGetter', // -> this['some/nested/module/someOtherGetter'] ]) }, // to computed: { ...mapGetters('some/nested/module', ['foo', 'bar']) }, ``` #### `createNamespacedHelpers` 為該 module 量身打造 helpers 藉由 `createNamespacedHelpers` 的幫助,開發者可直接依照特定的 namespace 創建專屬的 helpers: ```javascript= import { createNamespacedHelpers } from 'vuex' // 建造出來的 mapState, mapActions 是專屬給 'some/nested/module' const { mapState, mapActions } = createNamespacedHelpers('some/nested/module') export default { computed: { // look up in `some/nested/module` // 直接建立專屬 'some/nested/module' 的 helper,所以不用特別再寫要找哪個 module ...mapState({ a: state => state.a, // -> this.a b: state => state.b // -> this.b }) }, methods: { // look up in `some/nested/module` ...mapActions([ 'foo', // -> this.foo() 'bar' // -> this.bar() ]) } } ``` ==如果沒有特別需要改名字的需求,直接用 `helper('moduleName', ['methodName'])` 是最方便的。== ## 如果是單純地讀取 vuex state,要使用 getters 還是直接在 component 內讀取 vuex state? Vuex getters 雖然很方便,但是如果只是單純地回傳 state(無需特別的篩選需求),還是建議使用 `this.$store.state.keyName` 或者透過 `mapState` 讀取 vuex 的 state。 ```javascript= // src/store/index.js import { createStore } from 'vuex'; const store = createaStore = createStore({ state () { return { todos: [ { id: 1, item: 'eat dinner', done: false }, { id: 2, item: 'work-up', done: true }, { id: 3, item: 'learn vue', done: true }, { id: 4, item: 'call mom', done: false }, ] } }, getters: { todos: (stata) => state.todos } }) ``` ```javascript= // src/components/MyComponent.vue computed: { getTodos() { return this.$store.getters.todos }, } ``` 單純地讀取 state 還是直接讀取 state 或者 `mapState` 比較合適,回歸它們最初的職責,負責讀取 state: ```javascript= // src/components/MyComponent.vue import { mapState } from 'vuex'; computed: { ...mapState(['todos']) } ``` ## 參考資料 1. 008 天絕對看不完的 Vue.js 3 指南 2. [[Vue] Vuex 是什麼? 怎麼用?(全系列共 5 篇)](https://medium.com/itsems-frontend/vue-vuex1-state-mutations-364163b3acac) 3. [Vuex](https://vuex.vuejs.org/) 4. [透過 namespacing 讓 Vuex 更結構化](https://jigsawye.com/2017/08/30/vuex-module-namespacing) 5. [Vuex getters are great, but don’t overuse them](https://codeburst.io/vuex-getters-are-great-but-dont-overuse-them-9c946689b414)