目錄
- Vuex
- 介紹
- Vuex 中的核心概念
- 在vue2中使用Vuex
- 安裝 Vuex
- 創建一個 Vuex Store
- 在 Vue 實例中使用 Vuex
- 編寫 Vuex 的 state、mutations 和 actions
- 在組件中使用 Vuex
- Vuex的核心
- State
- 組件中獲取 Vuex 的狀態
- mapState 輔助函數
- 對象展開運算符
- Getter
- 基本使用
- 示例
- 通過屬性訪問
- 通過方法訪問
- mapGetters 輔助函數
- Mutation
- 定義mutation
- mutations 中回調函數參數:
- commit 提交 mutation
- Mutation 必須是同步函數
- mapMutations 輔助函數
- Actions
- Action 函數
- dispatch 觸發 Action
- action 內部執行異步操作
- 購物車示例,涉及到調用異步 API 和分發多重 mutation
- mapActions 輔助函數
- 組合 Action
- Modules
- 基本使用
- 示例:
- 命名空間
- 示例
Vuex
介紹
- Vuex 是一個用于 Vue.js 應用程序的狀態管理模式。
- 它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態的一致性。
- Vuex 還集成了Vue的官方瀏覽器插件 vue-devtools,提供了一些強大的調試工具。
- Vue 2 匹配的 Vuex 3 的文檔:文檔鏈接
Vuex 中的核心概念
-
State(狀態):用于存儲應用程序的狀態,可以通過
this.$store.state
訪問。 -
Getters(計算屬性):類似于 Vue 組件中的計算屬性,可以派生出一些狀態,可以通過
this.$store.getters
訪問。 -
Mutations(突變):用于修改狀態,只能進行同步操作,可以通過
this.$store.commit
觸發。 -
Actions(異步操作):用于處理異步操作,可以通過
this.$store.dispatch
觸發,并且可以調用多個突變。 -
Modules(模塊化):可以將 store 分割成多個模塊,每個模塊擁有自己的 state、getters、mutations和actions。
使用 Vuex 可以幫助我們更好地組織和管理 Vue 應用的狀態,并且方便狀態的復用和共享。
在vue2中使用Vuex
安裝 Vuex
可以使用 npm 或者 yarn 進行安裝。
npm install vuex
//或者
npm install vuex@3.0.0 --save
//或者
yarn add vuex
創建一個 Vuex Store
在src/store目錄下創建一個名為 index.js
的文件,并在其中導入 Vue 和 Vuex,并創建一個新的 Vuex.Store 實例。
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({state: {count: 1},mutations: {increment(state, value) {state.count += value}}
})
export default store;
在 Vue 實例中使用 Vuex
在 main.js 文件中導入剛才創建的 index.js
文件,并在 Vue 實例的配置對象中引入 store
。
import Vue from 'vue'
import App from './App.vue'
import store from './store'new Vue({store,render: h => h(App),
}).$mount('#app')
編寫 Vuex 的 state、mutations 和 actions
對于需要進行狀態管理的數據,可以在 store.js
文件中定義 state,并在 mutations 中編寫修改 state 的方法,在 actions 中編寫處理業務邏輯的方法。
export default new Vuex.Store({state: {count: 0,},mutations: {increment(state) {state.count++},},actions: {incrementAsync({ commit }) {setTimeout(() => {commit('increment')}, 1000)},},})
在組件中使用 Vuex
在需要使用 Vuex 中的狀態或觸發 Vuex 中 mutations/actions 的組件中,可以通過 this.$store.state
來獲取 state,通過 this.$store.commit
來觸發 mutations,通過 this.$store.dispatch
來觸發 actions。
<template><div><p>Count: {{ count }}</p><button @click="increment">Increment</button></div></template><script>export default {computed: {count() {return this.$store.state.count},},methods: {increment() {this.$store.commit('increment')},},}</script>
通過上述步驟,就可以在 Vue 2 中使用 Vuex 進行狀態管理了。可以在組件中方便地共享和修改狀態,并且處理異步操作。
Vuex的核心
State
組件中獲取 Vuex 的狀態
-
在計算屬性中返回某個狀態:
// 創建一個 Counter 組件 const Counter = {template: `<div>{{ count }}</div>`,computed: {count () {return store.state.count}} }
-
在每個需要使用 state 的組件中需要頻繁地導入
-
在根實例中注冊 store 選項,該 store 實例會注入到根組件下的所有子組件中
const app = new Vue({el: '#app',store, // 把 store 對象提供給 “store” 選項,這可以把 store 的實例注入所有的子組件components: { Counter },template: `<div class="app"><counter></counter></div>` })
-
子組件能通過 this.$store 訪問:
const Counter = {template: `<div>{{ count }}</div>`,computed: {count () {return this.$store.state.count}} }
mapState 輔助函數
-
用于組件需要獲取多個狀態的時候
<template><div class="hello"><div><h3>組件中使用 store</h3>當前count:{{ $store.state.count }}</div><div><h3>組件中使用 mapState</h3><div>當前count:{{ count }}</div><div>當前countAlias:{{ countAlias }}</div><div>當前countPlusLocalState:{{ countPlusLocalState }}</div></div><div><button v-on:click="clickCount(0)">減1</button><button v-on:click="clickCount(1)">加1</button></div></div> </template><script> import { mapState } from "vuex";export default {name: "CountView",methods: {clickCount(val) {this.$store.commit("increment", val === 0 ? -1 : 1);},},data: () => ({localCount: 3,}),computed: {...mapState({// 箭頭函數可使代碼更簡練count: (state) => state.count,// 傳字符串參數 'count' 等同于 `state => state.count`countAlias: "count",// 使用常規函數,count + data中的localCountcountPlusLocalState(state) {return state.count + this.localCount;},}),}, }; </script>
-
也可以給 mapState 傳一個字符串數組:
computed: mapState([// 映射 this.count 為 store.state.count'count' ])
對象展開運算符
```js
computed: {localComputed () { /* ... */ },// 使用對象展開運算符將此對象混入到外部對象中...mapState({// ...})
}
```
Getter
基本使用
- 就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算。
示例
-
Getter 接受 2 個參數, state 作為其第一個參數,getter 作為第二個參數
const store = new Vuex.Store({state: {todos: [{ id: 1, text: '...', done: true },{ id: 2, text: '...', done: false }]},getters: {doneTodos: (state, getters) => {return state.todos.filter(todo => todo.done)}} })
通過屬性訪問
-
Getter 會暴露為 store.getters 對象,可以以屬性的形式訪問這些值:
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
通過方法訪問
-
也可以通過讓 getter 返回一個函數,來實現給 getter 傳參
getters: {// ...getTodoById: (state) => (id) => {return state.todos.find(todo => todo.id === id)} }
-
使用
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
mapGetters 輔助函數
-
mapGetters 輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性:
import { mapGetters } from 'vuex'export default {// ...computed: {// 使用對象展開運算符將 getter 混入 computed 對象中...mapGetters(['doneTodosCount','anotherGetter',// ...])} }
-
如果你想將一個 getter 屬性另取一個名字,使用對象形式:
...mapGetters({// 把 `this.doneCount` 映射為 `this.$store.getters.doneTodosCount`doneCount: 'doneTodosCount' })
Mutation
定義mutation
更改狀態的唯一方法是提交 mutation
mutations 中回調函數參數:
- state 為第一個參數,
- payload(載荷)為第二個參數(可以為基本數據類型,也可以為對象)
const store = new Vuex.Store({state: {count: 1},mutations: {increment(state, payload) {state.count += payload}}
})
commit 提交 mutation
調用 store.commit 方法:
store.commit('increment', 2)
Mutation 必須是同步函數
一條重要的原則就是要記住 mutation 必須是同步函數
mutations: {someMutation (state) {api.callAsyncMethod(() => {state.count++})}
}
- 原因是當 mutation 觸發的時候,回調函數還沒有被調用,devtools 不知道什么時候回調函數實際上被調用,這樣狀態的變化就變得不可追蹤
- 解決方法:使用 Actions
mapMutations 輔助函數
使用 mapMutations 輔助函數將組件中的 methods 映射為 store.commit 調用:(需要先在根節點注入 store):
import { mapMutations } from 'vuex'export default {// ...methods: {...mapMutations(['increment', // 將 `this.increment()` 映射為 `this.$store.commit('increment')`// `mapMutations` 也支持載荷:'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.commit('incrementBy', amount)`]),...mapMutations({add: 'increment' // 將 `this.add()` 映射為 `this.$store.commit('increment')`})}
}
Actions
Action 類似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接變更狀態。
- Action 可以包含任意異步操作。
Action 函數
Action 函數參數
- context 對象(與 store 實例具有相同方法和屬性,因此你可以調用 context.commit 提交一個 mutation)
- payload 載荷(可以基本數據,也可以對象)
定義
const store = new Vuex.Store({state: {count: 0},mutations: {increment(state, payload) {state.count += payload;}},actions: {increment(context, payload) {context.commit('increment', payload)}}
})export default store;
dispatch 觸發 Action
Action 通過 store.dispatch 方法觸發:
store.dispatch('increment', 3)
action 內部執行異步操作
actions: {incrementAsync ({ commit }) {setTimeout(() => {commit('increment')}, 1000)}
}
購物車示例,涉及到調用異步 API 和分發多重 mutation
actions: {checkout ({ commit, state }, products) {// 把當前購物車的物品備份起來const savedCartItems = [...state.cart.added]// 發出結賬請求,然后樂觀地清空購物車commit(types.CHECKOUT_REQUEST)// 購物 API 接受一個成功回調和一個失敗回調shop.buyProducts(products,// 成功操作() => commit(types.CHECKOUT_SUCCESS),// 失敗操作() => commit(types.CHECKOUT_FAILURE, savedCartItems))}
}
mapActions 輔助函數
使用 mapActions 輔助函數將組件的 methods 映射為 store.dispatch 調用(需要先在根節點注入 store):
import { mapActions } from 'vuex'export default {// ...methods: {...mapActions(['increment', // 將 `this.increment()` 映射為 `this.$store.dispatch('increment')`// `mapActions` 也支持載荷:'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.dispatch('incrementBy', amount)`]),...mapActions({add: 'increment' // 將 `this.add()` 映射為 `this.$store.dispatch('increment')`})}
}
組合 Action
Action 通常是異步的,那么如何知道 action 什么時候結束呢?更重要的是,我們如何才能組合多個 action,以處理更加復雜的異步流程?
首先,你需要明白 store.dispatch 可以處理被觸發的 action 的處理函數返回的 Promise,并且 store.dispatch 仍舊返回 Promise:
actions: {actionA ({ commit }) {return new Promise((resolve, reject) => {setTimeout(() => {commit('someMutation')resolve()}, 1000)})}
}
現在這么寫
store.dispatch('actionA').then(() => {// ...
})
在另外一個 action 中也可以:
actions: {// ...actionB ({ dispatch, commit }) {return dispatch('actionA').then(() => {commit('someOtherMutation')})}
}
最后,如果我們利用 async / await (opens new window),我們可以如下組合 action:
// 假設 getData() 和 getOtherData() 返回的是 Promise
actions: {async actionA ({ commit }) {commit('gotData', await getData())},async actionB ({ dispatch, commit }) {await dispatch('actionA') // 等待 actionA 完成commit('gotOtherData', await getOtherData())}
}
Modules
基本使用
- Vuex 允許我們將 store 分割成模塊(module)
- 每個模塊擁有自己的 state、mutation、action、getter
示例:
store/modules/moduleA.js
const moduleA = {state: {countA: 1},getters: {},mutations: {},actions: {}
}export default moduleA;
store/modules/moduleB.js
const moduleB = {state: {countB: 2},getters: {sumWithRootCount(state, getters, rootState) {// 這里的 state 和 getters 對象是模塊的局部狀態,rootState 為根節點狀態console.log('B-state', state)console.log('B-getters', getters)console.log('B-rootState', rootState)return state.countB + rootState.count}},mutations: {increment(state, payload) {// 這里的 `state` 對象是模塊的局部狀態state.countB += payload;}},actions: {incrementIfOddOnRootSum({ state, commit, rootState }, payload) {console.log(payload)// 這里的 `state` 對象是模塊的局部狀態,rootState 為根節點狀態console.log(state)commit('increment', rootState.count + payload)}}
}export default moduleB;
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'Vue.use(Vuex)const store = new Vuex.Store({state: {count: 5},modules: {moduleA: moduleA,moduleB: moduleB,}
})export default store;
組件中使用
<template><div class="hello"><div><h3>組件中使用 store</h3><div>moduleA中的countA {{ countA }}</div><div>moduleB中的countB {{ countB }}</div></div><div><button v-on:click="clickCommit(1)">commit 加1</button><button v-on:click="clickDispatch(1)">dispatch 加1</button></div></div>
</template><script>export default {name: "CountView",methods: {clickCommit(val) {this.$store.commit("increment", val);},clickDispatch(val) {this.$store.dispatch("incrementIfOddOnRootSum", val);},},computed: {countA() {// moduleA 中的 countAreturn this.$store.state.moduleA.countA;},countB() {// moduleB 中的 countBreturn this.$store.state.moduleB.countB;},},
};
</script>
命名空間
- 默認情況下,模塊內部的 action、mutation 和 getter 是注冊在全局命名空間的——這樣使得多個模塊能夠對同一 mutation 或 action 作出響應
- 可以通過添加 namespaced: true 的方式使其成為帶命名空間的模塊。
- 當模塊被注冊后,它的所有 getter、action 及 mutation 都會自動根據模塊注冊的路徑調整命名。
示例
store/modules/moduleA.js
const moduleA = {namespaced: true, // 設為命名空間state: {countA: 1},getters: {},mutations: {},actions: {}
}export default moduleA;
store/modules/moduleB.js
const moduleB = {namespaced: true, // 設為命名空間state: {countB: 2},getters: {sumWithRootCount(state, getters, rootState) {// 這里的 state 和 getters 對象是模塊的局部狀態,rootState 為根節點狀態console.log('B-state', state)console.log('B-getters', getters)console.log('B-rootState', rootState)return state.countB + rootState.count}},mutations: {increment(state, payload) {// 這里的 `state` 對象是模塊的局部狀態state.countB += payload;}},actions: {incrementIfOddOnRootSum({ state, commit, rootState }, payload) {console.log(payload)// 這里的 `state` 對象是模塊的局部狀態,rootState 為根節點狀態console.log(state)commit('increment', rootState.count + payload)}}
}export default moduleB;
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'Vue.use(Vuex)const store = new Vuex.Store({state: {count: 5},modules: {moduleA: moduleA,moduleB: moduleB,}
})export default store;
組件中使用
<template><div class="hello"><div><h3>組件中使用 store</h3><div>moduleA中的countA {{ countA }}</div><div>moduleB中的countB {{ countB }}</div></div><div><button v-on:click="increment(1)">commit 加1</button><button v-on:click="incrementIfOddOnRootSum(1)">dispatch 加1</button></div></div>
</template><script>
import { mapActions, mapMutations, mapState } from "vuex";export default {name: "CountView",methods: {// 將模塊的空間名稱字符串作為第一個參數傳遞給 mapMutations...mapMutations("moduleB", ["increment"]),// 將模塊的空間名稱字符串作為第一個參數傳遞給 mapActions...mapActions("moduleB", ["incrementIfOddOnRootSum"]),},computed: {// 將模塊的空間名稱字符串作為第一個參數傳遞給 mapState...mapState("moduleA", {countA: (state) => state.countA,}),...mapState("moduleB", {countB: (state) => state.countB,}),},
};
</script>