# [Vue] Vuex 筆記
###### tags: `Vue` `Vuex` `前端筆記`

(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*

(Vuex 的 one-way data flow 示意圖)
## 基本安裝 / 使用
### 1. 透過 Vue CLI 建立專案時就安裝

那麼安裝結束後 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>
```

(透過 getters 取得 state 的資料)

(點擊事件後透過 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

#### 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')`

#### 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 執行完再繼續執行內部函式的任務

### `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)