Vue-6 Vuex(state, getters, mutations, actions, modules)
在項目研發過程中,組件和組件之間的結構關係非常複雜,到處存在數據交互。 props
,$emit
,路由傳參是遠遠不足以支撐複雜的數據交互。
Vue
單獨提供了 vuex
來做項目的狀態管理,vuex
提供了公共數據的存儲和操作方式,讓組件之間的數據交互變得更簡潔。
vuex
的核心理念就是存儲一些供各個組件使用的數據,無論組件的嵌套結構如何,都能訪問 vuex
中的數據。任何一個組件對其中的數據做了修改,在其他組件中的渲染會立即響應。
一、Vuex的安裝和基本配置
和 vue-router
一樣,要在項目中使用 vuex
,需要先下載依賴包。
npm i vuex --save
安裝成功之後在 src
文件夾中創建項目目錄:
src
└── App.vue
└── main.js
└── assets
└── components
// ...
└── router
// ...
└── store // 狀態管理庫的目錄
index.js // 狀態管理庫的根配置文件
vuex
的基本配置:
// store>index.js
import Vue from 'vue'
import Vuex from 'vuex' // 引入 vuex
Vue.use(Vuex);
// 創建一個狀態管理庫實例並且導出,以備使用
export default new Vuex.Store({
// code
});
配置好了之後要將這個 Vuex.Store
實例對象傳入根組件的實例化中,整個項目才能使用 vuex
:
// main.js
import Vue from 'vue'
import App from './App'
// 導入 store (省略)
import store from './vuex'
Vue.comfig.productionTip = false;
new Vue({
el: '#app',
// 將 store 傳入根組件實例化的參數中
store,
components: { App },
template: '<App/>'
});
vuex
爲 Store
類提供了五個配置,包含了可能出現的各種操作。
二、state
state
中存放的是 store
中所有的數據列表。
任何組件無論嵌套關係多複雜,都可以訪問 state
中的數據。
// store>index.js 中的 state
state: {
num: 100,
bol: true
}
在任意組件中獲取:
<!-- Any.vue -->
<template>
<div class="any">
<h2>num: {{ num }}</h2>
<h2>bol: {{ bol }}</h2>
</div>
</template>
<script>
export default {
name: 'Any',
computed: {
// 建議將對於 store 中的數據的獲取或者操作寫在計算屬性中,這樣當 store 中的數據變化時,也能及時響應
num() {
return this.$store.state.num;
},
bol() {
return this.$store.state.bol;
}
}
}
</script>
在其他任何組件中,對 state
中的數據做操作時,當前組件也會隨之響應。
store
的好處之一就是無論組件和組件之間的嵌套結構是怎樣的,都能共同訪問 store
中的數據。
但是如果只是想訪問 state
中的數據,且訪問了很多個數據,那麼爲每一個數據單獨的寫一個計算屬性,就顯得很不理智。
vuex
提供 mapState
來批量的訪問 state
中的數據
<!-- Any.vue -->
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState([
'num',
'bol'
]);
}
}
</script>
調用方式不變。
三、getters
給一個示例,state
中有一個數組,數組中有各種類型的數據,有兩個組件(A和B),都只想要這個數組中的 number
列表。
state: {
arr: [1, 4, 5, 'hello', true, 'world', 98]
}
我們可以在 A 和 B 的計算屬性中先拿到 state
中的 arr
,對其做處理,返回數字列表:
<!-- A.vue -->
<script>
export default {
name: 'A',
computed: {
numArr() {
const arr = this.$store.state.arr;
return arr.filter(function(item) {
if(typeof item == 'number') return item;
});
}
}
}
</script>
如果 B 組件的需求和 A 組件一樣,那麼 numArr
中的這段代碼,在 B 組件中,又要重新寫一遍。
這樣就形成了代碼冗餘。
所以 vuex
提供了 getters
,來根據代碼需求過濾 state
中的數據,向外拋出過濾之後的數據。
// store>index.js 中導出的實例
export default new Vux.Store({
state: {
arr: [1, 4, 5, 'hello', true, 'world', 98]
},
getters: {
// getters 中的函數接收的第一個參數就是 state,可以通過它直接訪問數據列表
numArr: state => {
const arr = state.arr;
return arr.filter(function(item) {
if(typeof item == 'number') return item;
});
}
}
});
現在 A 、B 組件想要訪問 state
中的 arr
中的數字列表,可以直接訪問 getters
。
<!-- A.vue -->
<script>
export default {
name: 'A',
computed: {
// 求 state 中的 arr 中的所有數字的和
numSum() {
const arr = this.$store.getters.numArr;
// 求和代碼
}
}
}
</script>
<!-- B.vue -->
<script>
export default {
name: 'B',
computed: {
// 求 state 中的 arr 中的所有數字的最大值和最小值
numSum() {
const arr = this.$store.getters.numArr;
const min = Math.min(...arr);
const max = Math.max(...arr);
}
}
}
</script>
可以認爲 getters
是 store
的計算屬性,當 state
中的數據變化的時候,getters
返回的數據也會隨之變化,且只有當它的依賴值發生了變化之後纔會變化。
如果需求再變化:A 組件希望拿到 arr
中所有的數字,B 組件希望拿到 arr
中所有的字符串。那可能需要向 getters
中傳遞想要的數據類型,實現按需獲取數據的效果。
所以我們也可以以函數的形式訪問 getters
,並且向其傳參。
// store 中的 getters
getters: {
getArrByType(state) {
return function(type) {
const arr = state.arr;
return arr.filter(function(item) {
if(typeof item == type) return item;
});
}
}
/*
關於以函數的形式訪問getters官網給出的是箭頭函數簡寫形式(兩種意義一樣):
getArrByType: (state) => (type) => {
const arr = state.arr;
return arr.filter(function(item) {
if(typeof item == type) return item;
});
}
*/
}
訪問:
// 任意組件的 computed
computed: {
getData() {
const numArr = this.$store.getters.getArrByType('number');
const strArr = this.$store.getters.getArrByType('string');
}
}
同樣的,如果 getters
只是獲取而不傳參,也不用爲其寫很多個計算屬性。
vuex
提供 mapGetters
來批量獲取 getters
。
// 任意組件的 js
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對象展開運算符將 getter 混入 computed 對象中
...mapGetters([
'getData1',
'getData2',
// ...
])
}
}
調用時直接將這些數組中的參數作爲變量名去使用即可。
如果覺得 store
中對於 getters
的命名太冗長,也可以在某個組件中爲其單獨命名
// 在當前組件中,gd 就代表 getArrByType
mapGetters({
gd: 'getArrByType'
})
四、mutations
根據代碼的需求,有時候我們可能也需要對 vuex
中的數據做一些更改。
vuex
規定,只能在其提供的 mutations
中修改 state
中的數據,否則嚴格模式下會直接報錯。
// store>index.js 中 store 實例的導出
export default new Vuex.Store({
state: {
arr: [1,4,6]
},
mutations: {
// 向 arr 中添加數據
arrPush(state) {
// mutations 中接收的函數的第一個參數也是 state
state.arr.push(Math.floor(Math.random() * 100));
}
}
});
在任意組件中調用 mutations
中的函數需要使用到 store
的 commit
。
// 任意組件的 created
created() {
// 調用 store 的 mutations 中的 arrPush
this.$store.commit('arrPush');
}
也可以向 mutations
中的函數傳參
mutations: {
arrPush(state, data) {
// data 就是在調用 mutations 時傳遞過來的參數
state.arr.push(data);
}
}
調用:
// 任意組件的 created
created() {
this.$store.commit('arrPush', Math.random());
}
vuex
建議: 向 mutations
中傳遞的參數儘量是一個對象,這樣便於傳輸大量數據。
需要注意的是:mutations 中不能存在異步操作。
我們也可以使用 mapMutations
將組件中的 methods
映射成 mutations
中的函數。
methods: {
...mapMutations([
// 在當前組件中,ap 就代表 arrPush
ap: 'arrPush'
])
}
五、actions
vuex
不允許 mutations
中出現異步操作,那麼有個異步操作剛好需要對 state
做改變,就需要寫在 vuex
提供的 actions
中。
所以應該是在 actions
中寫異步代碼,在異步的某個過程中,如果需要對 state
做操作,就調用 mutations
中的函數。
// store>index.js 中的導出
export default new Vuex.Store({
state: '500 miles',
mutations: {
setStr(state, newStr) {
state.str = newStr;
}
},
actions: {
_setStr(context) {
// Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象
setTimeout(() => {
context.commit('setStr', '500 miles away from home');
});
}
}
});
調用:
// 任意組件的 created
created() {
this.$store.dispatch('setStr');
}
actions
也可以接收 dispatch
時被傳遞的數據:
actions: {
// 可以通過解構的方式從 context 中解構到 commit
_setStr({commit}, newStr) {
setTimeout(() => {
// newStr 就是調用 actions 時傳遞的參數
context.commit('setStr', newStr);
});
}
}
調用:
// 任意組件的 created
created() {
this.$store.dispatch('setStr', '500 miles away from home');
}
六、modules
如果項目對於 state
的依賴很強,使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常複雜時,store 對象就有可能變得相當臃腫。
Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊。
將不同的需求的狀態單獨的聲明成一個變量,然後將其導入到根對象的 modules
中,就能生效。
// store>index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const moduleA = {
state: {
msg: 'data of a'
},
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: {
msg: 'data of b'
},
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
state: {
msg: 'data of root'
}
modules: {
a: moduleA,
b: moduleB
}
});
// 創建一個狀態管理庫實例並且導出,以備使用
export default store
對於不同模塊的 state
中的數據的訪問:
// 任意組件的 created
created() {
const data1 = this.$store.state.msg;
const data2 = this.$store.state.a.msg;
const data3 = this.$store.state.b.msg;
}
除了state之外,getters、mutations、actions的訪問方式不變。
如果要在子模塊的 getters
中訪問根 state
中的數據:
// 任意子模塊的 getters
getters: {
getData(state, getters, rootState) {
// state: 當前模塊的 state
// rootState: 根模塊的 state
}
}
如果要在子模塊的 actions
中訪問:
// 子模塊的actions
actions: {
fn(context) {
// context.state: 當前模塊的 state
// context.rootState: 根模塊的 state
}
}