Vuex 狀態(tài)管理模式
在使用vue開發(fā)過(guò)程中,經(jīng)常會(huì)遇到一個(gè)狀態(tài)在多個(gè)組件之間使用,這時(shí)候就需要用vuex來(lái)狀態(tài)管理模式來(lái)集中管理解決跨組件通信問(wèn)題,跨組件通信一般是指非父子組件間 的通信。
一、核心概念
在這里插入圖片描述
- store:vuex使用一個(gè)倉(cāng)庫(kù)store對(duì)象管理應(yīng)用的狀態(tài)和操作過(guò)程,一個(gè) store 包括 state, getter, mutation, action 四個(gè)屬性。vuex里的數(shù)據(jù)都是響應(yīng)式的,任何組件使用同意store的數(shù)據(jù)時(shí),只要store的數(shù)據(jù)變化,對(duì)應(yīng)的組件也會(huì)實(shí)時(shí)更新。
- state:狀態(tài),vuex的數(shù)據(jù)源。
- getter:getter就是對(duì)狀態(tài)進(jìn)行處理的提取出來(lái)的公共部分,對(duì)state進(jìn)行過(guò)濾輸出。
- mutation:提交mutation是更改vuex 的store中的狀態(tài)的唯一方法,并且只能是同步操作。
- action:對(duì)state的異步操作,并通過(guò)在action提交mutation變更狀態(tài)。
- module:當(dāng)store對(duì)象過(guò)于龐大時(shí),可以分成多個(gè)module,每個(gè)module也會(huì)有state, getter, mutation, action 四個(gè)屬性。
二、安裝
在使用vue-cli腳手架生成項(xiàng)目使用選擇vuex,也可以在使用以下命令安裝
npm install vuex --save
結(jié)構(gòu)如下:
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
main.js
通過(guò)在main.js注冊(cè) store,該 store 實(shí)例會(huì)注入到根組件下的所有子組件中,且子組件能通過(guò) this.$store 訪問(wèn)到。
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
三、State
state保存狀態(tài),先在state中定義一個(gè)狀態(tài)count
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
mutations: {},
actions: {},
modules: {}
}
現(xiàn)在有一個(gè)集中管理的狀態(tài)count,其他組件可以通過(guò)計(jì)算屬性來(lái)返回此狀態(tài),由于在main.js注冊(cè)過(guò)store,所以可以使用this.$store就可以獲取到state。
<template>
<div>
<p>{{count}}</p>
</div>
</template>
<script>
export default {
computed: {
count() {
return this.$store.state.count;
}
}
}
</script>
mapState 輔助函數(shù)
當(dāng)一個(gè)組件需要多個(gè)狀態(tài)時(shí),為這些狀態(tài)都做計(jì)算屬性會(huì)很冗余麻煩,可以使用 mapState 輔助函數(shù)來(lái)幫助生成計(jì)算屬性。
<template>
<div>
<p>{{count}}</p>
<p>{{calculation}}</p>
</div>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
data() {
return {
msg: "計(jì)數(shù):"
}
},
computed: mapState({
// 簡(jiǎn)寫
// count: state => state.count,
count(state) {
return state.count
},
// 使用組件內(nèi)狀態(tài)+store狀態(tài)
calculation(state) {
return this.msg + state.count
}
})
}
</script>
對(duì)象展開運(yùn)算符
...對(duì)象展開運(yùn)算符,...mapState語(yǔ)法糖簡(jiǎn)化了寫法
<template>
<div>
<p>{{count}}</p>
</div>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
computed: {
...mapState(['count'])
}
}
</script>
四、Getter
getter就是對(duì)狀態(tài)進(jìn)行帥選過(guò)濾操作,處理過(guò)后返回給組件使用。
先定義一組list,然后再對(duì)list篩選。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
list: [{
name: "魯班七號(hào)",
game: "王者榮耀"
},
{
name: "亞索",
game: "英雄聯(lián)盟"
},
{
name: "德瑪西亞之力",
game: "英雄聯(lián)盟"
}, {
name: "亞瑟",
game: "王者榮耀"
},
{
name: "阿古朵",
game: "王者榮耀"
},
{
name: "努努",
game: "英雄聯(lián)盟"
}
]
},
getters: {
lol: state => {
return state.list.filter(p => p.game=="英雄聯(lián)盟");
}
},
mutations: {},
actions: {},
modules: {}
})
getter會(huì)暴露出store.getters 對(duì)象,可以以屬性的方式訪問(wèn)
<template>
<div>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item.name }}</li>
</ul>
</div>
</template>
<script>
export default {
computed: {
list() {
return this.$store.getters.lol;
},
}
}
</script>
mapGetters 輔助函數(shù)
mapGetters輔助函數(shù)僅僅是將 store 中的 getter 映射到局部計(jì)算屬性
<template>
<div>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item.name }}</li>
</ul>
</div>
</template>
<script>
import {
mapGetters
} from 'vuex'
export default {
computed: {
...mapGetters({
list: 'lol'
})
}
}
</script>
五、Mutation
如果我們想修改store里的state狀態(tài)值時(shí),我們不可以直接在組件內(nèi)去修改,而是通過(guò)提交mutation來(lái)進(jìn)行修改,mutation類似于事件。我們來(lái)實(shí)現(xiàn)一個(gè)加減計(jì)數(shù)。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count:0
},
getters: {},
mutations: {
// 增加
increase(state){
state.count++
},
// 減少
decrease(state){
state.count--
}
},
actions: {},
modules: {}
})
在組件內(nèi),通過(guò)this.$store.commit方法來(lái)執(zhí)行mutation,
<template>
<div>
<input type="button" value="+" @click="increase" />
<input type="button" value="-" @click="decrease" />
<p>計(jì)數(shù):{{count}}</p>
</div>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
methods: {
increase() {
this.$store.commit('increase');
},
decrease() {
this.$store.commit('decrease');
}
},
computed: {
...mapState(['count'])
}
}
</script>
Payload 提交載荷
在提價(jià)mutation時(shí)可以傳入額外的參數(shù),即荷載(payload)
例如我想count每次改變自定義值
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
getters: {},
mutations: {
// 增加
increase(state, payload) {
state.count += payload
},
// 減少
decrease(state, payload) {
state.count -= payload
}
},
actions: {},
modules: {}
})
<template>
<div>
<input type="button" value="+" @click="increase" />
<input type="button" value="-" @click="decrease" />
<p>計(jì)數(shù):{{count}}</p>
</div>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
methods: {
increase() {
this.$store.commit('increase',10); //每次加10
},
decrease() {
this.$store.commit('decrease',5); //每次減5
}
},
computed: {
...mapState(['count'])
}
}
</script>
官網(wǎng)建議大多數(shù)情況下載荷應(yīng)該是一個(gè)對(duì)象,這樣可以包含多個(gè)字段并且記錄的 mutation 會(huì)更易讀:
mutations: {
// 增加
increase(state, payload) {
state.count += payload.customcount
},
// 減少
decrease(state, payload) {
state.count -= payload.customcount
}
},
methods: {
increase() {
this.$store.commit('increase', {
customcount: 10
}); //每次加10
},
decrease() {
this.$store.commit('decrease', {
customcount: 5
}); //每次減5
}
},
也可以寫成直接包含type屬性,也就是mutations里的事件(例如:increase、decrease)
methods: {
increase() {
this.$store.commit({
type:"increase",
customcount: 10
}); //每次加10
},
decrease() {
this.$store.commit({
type:"decrease",
customcount: 5
}); //每次減5
}
},
Mutation 需遵守 Vue 的響應(yīng)規(guī)則
既然 Vuex 的 store 中的狀態(tài)是響應(yīng)式的,那么當(dāng)我們變更狀態(tài)時(shí),監(jiān)視狀態(tài)的 Vue 組件也會(huì)自動(dòng)更新。這也意味著 Vuex 中的 mutation 也需要與使用 Vue 一樣遵守一些注意事項(xiàng):
- 最好提前在你的 store 中初始化好所有所需屬性。
- 當(dāng)需要在對(duì)象上添加新屬性時(shí),你應(yīng)該
使用
Vue.set(obj, 'newProp', 123), 或者-
以新對(duì)象替換老對(duì)象。例如,利用對(duì)象展開運(yùn)算符可以這樣寫:
state.obj = { ...state.obj, newProp: 123 }
例如
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
people:[]
},
getters: {},
mutations: {
addNewProp(state,payload){
//新增對(duì)象屬性
// Vue.set(state.people,"name","Tom");
//用新對(duì)象替換老對(duì)象
state.people= {...state.people, name: 'Jerry'}
}
},
actions: {},
modules: {}
})
<template>
<div>
<input type="button" value="新增屬性" @click="addNewProp" />
<p>{{name}}</p>
</div>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
methods: {
addNewProp() {
this.$store.commit('addNewProp');
}
},
computed: {
name() {
return this.$store.state.people.name;
},
}}
</script>
Mutation必須是同步函數(shù)
官網(wǎng)給的例子,當(dāng)mutation觸發(fā)的時(shí)候,回調(diào)函數(shù)還沒(méi)有被調(diào)用,回調(diào)函數(shù)中進(jìn)行的狀態(tài)的改變都是不可追蹤的。
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
mapMutations 輔助函數(shù)
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')`
})
}
}
六、Action
action類似于mutation,不同在于
- Action可以提交mutation,而不是直接變更狀態(tài)
- Action可以包含任意異步操作
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
getters: {},
mutations: {
// 增加
increase(state) {
state.count++
},
// 減少
decrease(state) {
state.count--
}
},
actions: {
// action函數(shù)接受一個(gè)與store實(shí)例具有相同方法和屬性的context對(duì)象,調(diào)用context.commit 提交一個(gè)mutation
increase(context) {
context.commit('increase')
}
},
modules: {}
})
action通過(guò)store.dispatch觸發(fā),action 則會(huì)提交 mutation,mutation 會(huì)對(duì) state 進(jìn)行修改,組件再根據(jù) state 、getter 渲染頁(yè)面。
上邊的action寫法與之前直接提交mutation沒(méi)有什么區(qū)別,但是action內(nèi)部可以執(zhí)行異步操作,而mutation只能是同步操作。
// 執(zhí)行異步操作
actions: {
increas({ commit }) {
setTimeout(() => {
commit('increase')
}, 1000)
},
decrease({ commit }) {
setTimeout(() => {
commit('decrease')
}, 1000)
}
}
同時(shí)action也是支持荷載方式和對(duì)象方式進(jìn)行分發(fā):
// 以載荷形式分發(fā)
store.dispatch('increase', {
amount: 10
})
// 以對(duì)象形式分發(fā)
store.dispatch({
type: 'increase',
amount: 10
})
在組件中使用this.$store.dispatch('xxx') 分發(fā) action,或者使用mapActions輔助函數(shù)將組件的methods映射為 store.dispatch 調(diào)用,
<template>
<div>
<input type="button" value="+" @click="increase" />
<input type="button" value="-" @click="decrease" />
<p>計(jì)數(shù):{{count}}</p>
</div>
</template>
<script>
import {
mapActions
} from 'vuex'
import {
mapState
} from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increase', // 將 `this.increase()` 映射為 `this.$store.dispatch('increment')`
]),
...mapActions({
decrease: 'decrease' // 將 `this.decrease()` 映射為 `this.$store.dispatch('decrease')`
})
},computed:{
...mapState(["count"])
}
}
</script>
七、Module
當(dāng)應(yīng)用變得復(fù)雜時(shí),store對(duì)象會(huì)變得非常臃腫,vuex可以store分割成多個(gè)模塊(Module),每個(gè)模塊都擁有自己的state、mutation、action、getter。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const moduleA = {
state: {
a: 'a'
},
mutations: {},
actions: {},
getters: {}
}
const moduleB = {
state: {
b: 'b'
},
mutations: {},
actions: {},
getters: {}
}
export default new Vuex.Store({
state: {
},
getters: {},
mutations: {},
actions: {},
modules: { ma: moduleA, mb: moduleB }
})
<template>
<div>
<p>{{msg}}</p>
</div>
</template>
<script>
import {
mapActions
} from 'vuex'
import {
mapState
} from 'vuex'
export default {
computed: {
msg() {
return this.$store.state.ma.a;
}
}
}
</script>
對(duì)于模塊內(nèi)部的mutation與getter,接受的第一個(gè)參數(shù)時(shí)模塊的局部狀態(tài)對(duì)象
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increase (state) {
// 這里的 `state` 對(duì)象是模塊的局部狀態(tài)
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
對(duì)于模塊內(nèi)部的action,局部狀態(tài)可以通過(guò)context.state暴露出來(lái),根節(jié)點(diǎn)狀態(tài)則為context.rootState
const moduleA = {
actions: {
increaseIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
模塊內(nèi)部的getter,根節(jié)點(diǎn)狀態(tài)回作為第三個(gè)參數(shù)暴露出來(lái)
const moduleA = {
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
命令空間
默認(rèn)情況下,mutations、actions、getters這些都是注冊(cè)在全局上面的,可以直接調(diào)用,希望你的模塊具有更高的封裝度和復(fù)用性,你可以通過(guò)添加 namespaced: true 的方式使其成為帶命名空間的模塊。當(dāng)模塊被注冊(cè)后,它的所有 getter、action 及 mutation 都會(huì)自動(dòng)根據(jù)模塊注冊(cè)的路徑調(diào)整命名。
首先新建一個(gè)app.js用來(lái)聲明模塊
const state = {
keyword: "",
};
const mutations = {
SET_KEYWORD(state, payload) {
state.keyword = payload
},
DEL_KEYWORD(state) {
state.keyword = ""
}
};
//action可以提交mutation,在action中可以執(zhí)行store.commit,如果要使用某個(gè)action,需執(zhí)行store.dispath
const actions = {
setKeyword({ commit }, value) {
commit("SET_KEYWORD", value);
},
delKeyword({ commit }) {
commit("DEL_KEYWORD");
},
};
export const app = {
namespaced: true,
state,
mutations,
actions
};
然后在store.js里面引入
import { app } from './app.js';
export default new Vuex.Store({
modules: {
app: app
},
});
使用action 根據(jù)模塊注冊(cè)的路徑調(diào)用
store.dispatch('app/delKeyword')
在帶命名空間的模塊內(nèi)訪問(wèn)全局內(nèi)容(Global Assets)
如果你希望使用全局 state 和 getter,rootState 和 rootGetters 會(huì)作為第三和第四參數(shù)傳入 getter,也會(huì)通過(guò) context 對(duì)象的屬性傳入 action。
若需要在全局命名空間內(nèi)分發(fā) action 或提交 mutation,將 { root: true } 作為第三參數(shù)傳給 dispatch 或 commit 即可。(官網(wǎng)例子)
modules: {
foo: {
namespaced: true,
getters: {
// 在這個(gè)模塊的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四個(gè)參數(shù)來(lái)調(diào)用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// 在這個(gè)模塊中, dispatch 和 commit 也被局部化了
// 他們可以接受 `root` 屬性以訪問(wèn)根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
平時(shí)開發(fā)時(shí)只用到了這些,關(guān)于更多的module可以去擼官網(wǎng)。
八、項(xiàng)目結(jié)構(gòu)
平時(shí)開發(fā)中使用下面的項(xiàng)目結(jié)構(gòu)
store
modules
a.js
b.js
c.js
getters.js
index.js
a.js示例
const state = {
keyword: "",
};
const mutations = {
SET_KEYWORD(state, payload) {
state.keyword = payload
},
DEL_KEYWORD(state) {
state.keyword = ""
}
};
const actions = {
setKeyword({ commit }, value) {
commit("SET_KEYWORD", value);
},
delKeyword({ commit }) {
commit("DEL_KEYWORD");
},
};
export default {
namespaced: true,
state,
mutations,
actions
};
getters.js示例
const getters = {
keyword: state => state.a.keyword,
};
export default getters;
index.js示例
import Vue from "vue";
import Vuex from "vuex";
import getters from "./getters";
const path = require("path");
Vue.use(Vuex);
const files = require.context("./modules", false, /\.js$/);
let modules = {};
files.keys().forEach(key => {
let name = path.basename(key, ".js");
modules[name] = files(key).default || files(key);
});
const store = new Vuex.Store({
modules,
getters
});
export default store;