Vuex 狀態(tài)管理模式
在使用vue開發(fā)過程中到忽,經(jīng)常會遇到一個狀態(tài)在多個組件之間使用,這時候就需要用vuex來狀態(tài)管理模式來集中管理解決跨組件通信問題,跨組件通信一般是指非父子組件間 的通信。
一顿天、核心概念
在這里插入圖片描述
- store:vuex使用一個倉庫store對象管理應(yīng)用的狀態(tài)和操作過程,一個 store 包括 state, getter, mutation, action 四個屬性蔑担。vuex里的數(shù)據(jù)都是響應(yīng)式的牌废,任何組件使用同意store的數(shù)據(jù)時,只要store的數(shù)據(jù)變化啤握,對應(yīng)的組件也會實時更新鸟缕。
- state:狀態(tài),vuex的數(shù)據(jù)源。
- getter:getter就是對狀態(tài)進行處理的提取出來的公共部分懂从,對state進行過濾輸出授段。
- mutation:提交mutation是更改vuex 的store中的狀態(tài)的唯一方法,并且只能是同步操作番甩。
- action:對state的異步操作侵贵,并通過在action提交mutation變更狀態(tài)。
- module:當(dāng)store對象過于龐大時缘薛,可以分成多個module窍育,每個module也會有state, getter, mutation, action 四個屬性。
二掩宜、安裝
在使用vue-cli腳手架生成項目使用選擇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
通過在main.js注冊 store
,該 store 實例會注入到根組件下的所有子組件中牺汤,且子組件能通過 this.$store
訪問到。
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中定義一個狀態(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)在有一個集中管理的狀態(tài)count,其他組件可以通過計算屬性來返回此狀態(tài)码耐,由于在main.js注冊過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)一個組件需要多個狀態(tài)時骚腥,為這些狀態(tài)都做計算屬性會很冗余麻煩敦间,可以使用 mapState
輔助函數(shù)來幫助生成計算屬性。
<template>
<div>
<p>{{count}}</p>
<p>{{calculation}}</p>
</div>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
data() {
return {
msg: "計數(shù):"
}
},
computed: mapState({
// 簡寫
// count: state => state.count,
count(state) {
return state.count
},
// 使用組件內(nèi)狀態(tài)+store狀態(tài)
calculation(state) {
return this.msg + state.count
}
})
}
</script>
對象展開運算符
...
對象展開運算符束铭,...mapState
語法糖簡化了寫法
<template>
<div>
<p>{{count}}</p>
</div>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
computed: {
...mapState(['count'])
}
}
</script>
四廓块、Getter
getter就是對狀態(tài)進行帥選過濾操作,處理過后返回給組件使用契沫。
先定義一組list带猴,然后再對list篩選。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
list: [{
name: "魯班七號",
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會暴露出store.getters
對象懈万,可以以屬性的方式訪問
<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 映射到局部計算屬性
<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)值時,我們不可以直接在組件內(nèi)去修改会通,而是通過提交mutation來進行修改口予,mutation類似于事件。我們來實現(xiàn)一個加減計數(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)沪停,通過this.$store.commit方法來執(zhí)行mutation,
<template>
<div>
<input type="button" value="+" @click="increase" />
<input type="button" value="-" @click="decrease" />
<p>計數(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 提交載荷
在提價mutation時可以傳入額外的參數(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>計數(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)該是一個對象牙甫,這樣可以包含多個字段并且記錄的 mutation 會更易讀:
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)時泻轰,監(jiān)視狀態(tài)的 Vue 組件也會自動更新。這也意味著 Vuex 中的 mutation 也需要與使用 Vue 一樣遵守一些注意事項:
- 最好提前在你的 store 中初始化好所有所需屬性且轨。
- 當(dāng)需要在對象上添加新屬性時浮声,你應(yīng)該
使用
Vue.set(obj, 'newProp', 123)
, 或者-
以新對象替換老對象。例如旋奢,利用對象展開運算符可以這樣寫:
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){
//新增對象屬性
// Vue.set(state.people,"name","Tom");
//用新對象替換老對象
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ā)的時候,回調(diào)函數(shù)還沒有被調(diào)用至朗,回調(diào)函數(shù)中進行的狀態(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ù)接受一個與store實例具有相同方法和屬性的context對象,調(diào)用context.commit 提交一個mutation
increase(context) {
context.commit('increase')
}
},
modules: {}
})
action通過store.dispatch觸發(fā)嫌变,action 則會提交 mutation吨艇,mutation 會對 state 進行修改,組件再根據(jù) state 腾啥、getter 渲染頁面东涡。
上邊的action寫法與之前直接提交mutation沒有什么區(qū)別,但是action內(nèi)部可以執(zhí)行異步操作倘待,而mutation只能是同步操作疮跑。
// 執(zhí)行異步操作
actions: {
increas({ commit }) {
setTimeout(() => {
commit('increase')
}, 1000)
},
decrease({ commit }) {
setTimeout(() => {
commit('decrease')
}, 1000)
}
}
同時action也是支持荷載方式和對象方式進行分發(fā):
// 以載荷形式分發(fā)
store.dispatch('increase', {
amount: 10
})
// 以對象形式分發(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>計數(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ù)雜時,store對象會變得非常臃腫贞间,vuex可以store分割成多個模塊(Module)贿条,每個模塊都擁有自己的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>
對于模塊內(nèi)部的mutation與getter峻仇,接受的第一個參數(shù)時模塊的局部狀態(tài)對象
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increase (state) {
// 這里的 `state` 對象是模塊的局部狀態(tài)
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
對于模塊內(nèi)部的action公黑,局部狀態(tài)可以通過context.state
暴露出來,根節(jié)點狀態(tài)則為context.rootState
const moduleA = {
actions: {
increaseIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
模塊內(nèi)部的getter,根節(jié)點狀態(tài)回作為第三個參數(shù)暴露出來
const moduleA = {
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
命令空間
默認(rèn)情況下凡蚜,mutations人断、actions、getters這些都是注冊在全局上面的朝蜘,可以直接調(diào)用恶迈,希望你的模塊具有更高的封裝度和復(fù)用性,你可以通過添加 namespaced: true 的方式使其成為帶命名空間的模塊谱醇。當(dāng)模塊被注冊后暇仲,它的所有 getter、action 及 mutation 都會自動根據(jù)模塊注冊的路徑調(diào)整命名副渴。
首先新建一個app.js用來聲明模塊
const state = {
keyword: "",
};
const mutations = {
SET_KEYWORD(state, payload) {
state.keyword = payload
},
DEL_KEYWORD(state) {
state.keyword = ""
}
};
//action可以提交mutation奈附,在action中可以執(zhí)行store.commit,如果要使用某個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ù)模塊注冊的路徑調(diào)用
store.dispatch('app/delKeyword')
在帶命名空間的模塊內(nèi)訪問全局內(nèi)容(Global Assets)
如果你希望使用全局 state 和 getter斥滤,rootState
和 rootGetters
會作為第三和第四參數(shù)傳入 getter,也會通過 context
對象的屬性傳入 action轿秧。
若需要在全局命名空間內(nèi)分發(fā) action 或提交 mutation中跌,將 { root: true }
作為第三參數(shù)傳給 dispatch
或 commit
即可。(官網(wǎng)例子)
modules: {
foo: {
namespaced: true,
getters: {
// 在這個模塊的 getter 中菇篡,`getters` 被局部化了
// 你可以使用 getter 的第四個參數(shù)來調(diào)用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// 在這個模塊中, dispatch 和 commit 也被局部化了
// 他們可以接受 `root` 屬性以訪問根 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) { ... }
}
}
}
平時開發(fā)時只用到了這些一喘,關(guān)于更多的module可以去擼官網(wǎng)驱还。
八、項目結(jié)構(gòu)
平時開發(fā)中使用下面的項目結(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;