文章目錄
一、Vuex概述
1.1 官方解釋
Vuex 是一個(gè)專(zhuān)為 Vue.js 應(yīng)用程序開(kāi)發(fā)的狀態(tài)管理模式。
- 它采用集中式存儲(chǔ)管理 應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測(cè)的方式發(fā)生變化
-Vuex也集成到Vue的官方調(diào)試工具devtools extension,提供了諸如零配置的time-travel調(diào)試、狀態(tài)快照導(dǎo)入導(dǎo)出等高級(jí)調(diào)試功能。
1.2 大白話
狀態(tài)管理模式、集中式存儲(chǔ)管理這些名詞聽(tīng)起來(lái)就非常高大上,讓人捉摸不透。
其實(shí),可以簡(jiǎn)單的將其看成把需要多個(gè)組件共享的變量全部存儲(chǔ)在一個(gè)對(duì)象里面。
然后,將這個(gè)對(duì)象放在頂層的Vue實(shí)例中,讓其他組件可以使用。
那么,多個(gè)組件是不是就可以共享這個(gè)對(duì)象中的所有變量屬性了呢?
如果是這樣的話,為什么官方還要專(zhuān)門(mén)出一個(gè)插件Vuex呢?難道我們不能自己封裝一個(gè)對(duì)象來(lái)管理嗎?
當(dāng)然可以,只是我們要先想想VueJS帶給我們最大的便利是什么呢?沒(méi)錯(cuò),就是響應(yīng)式。
如果你自己封裝實(shí)現(xiàn)一個(gè)對(duì)象能不能保證它里面所有的屬性做到響應(yīng)式呢?當(dāng)然也可以,只是自己封裝可能稍微麻煩一些。
不用懷疑,Vuex就是為了提供這樣一個(gè)在多個(gè)組件間共享狀態(tài)的插件,用它就可以了。
1.3 組件間共享數(shù)據(jù)的方式
- 父向子傳值:
v-bind屬性綁定 - 子向父?jìng)髦担?code>v-on事件綁定
- 兄弟組件之間共享數(shù)據(jù):
EventBus-
$on接收數(shù)據(jù)的組件 -
$emit發(fā)送數(shù)據(jù)的組件
-
上述只適合小范圍內(nèi)數(shù)據(jù)共享,如果是復(fù)雜應(yīng)用的話,就不再合適了。
1.4 再看Vuex是什么
Vuex是實(shí)現(xiàn)組件全局狀態(tài)(數(shù)據(jù))管理的一種機(jī)制,可以方便的實(shí)現(xiàn)組件之間數(shù)據(jù)的共享
如圖:
在不使用Vuex進(jìn)行狀態(tài)管理時(shí),如果要從最下面的紫色組件傳遞數(shù)據(jù)的話,還是比較繁瑣,也不便于維護(hù)。
在使用Vuex進(jìn)行狀態(tài)管理時(shí),只需要一個(gè)共享Store組件,紫色組件將數(shù)據(jù)寫(xiě)入Store中,其他使用的組件直接從Store中讀取即可。
1.5 使用Vuex統(tǒng)一管理好處
- 能夠在
Vuex中集中管理共享的數(shù)據(jù),易于開(kāi)發(fā)和后期維護(hù) - 能夠高效地實(shí)現(xiàn)組件之間的數(shù)據(jù)共享,提高開(kāi)發(fā)效率
- 存儲(chǔ)在
Vuex中的數(shù)據(jù)都是響應(yīng)式的,能夠?qū)崟r(shí)保持?jǐn)?shù)據(jù)與頁(yè)面的同步
二、狀態(tài)管理
2.1 單頁(yè)面狀態(tài)管理
我們知道,要在單個(gè)組件中進(jìn)行狀態(tài)管理是一件非常簡(jiǎn)單的事情,如圖
- State:指的就是我們的狀態(tài),可以暫時(shí)理解為組件中
data中的屬性 - View:視圖層,可以針對(duì)
State的變化, 顯示不同的信息 - Actions:這里的
Actions主要是用戶的各種操作,如點(diǎn)擊、輸入等,會(huì)導(dǎo)致?tīng)顟B(tài)發(fā)生變化
簡(jiǎn)單加減法案例,代碼如下:
<template>
<div>
<div>當(dāng)前計(jì)數(shù)為:{{counter}}</div>
<button @click="counter+=1">+1</button>
<button @click="counter-=1">11</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
counter: 0
};
}
};
</script>
- 在這個(gè)案例中,有沒(méi)有狀態(tài)需要管理呢?肯定是有的,就是個(gè)數(shù)
counter -
counter需要某種方式被記錄下來(lái),也就是上述中的的State部分 - counter的值需要被顯示在潔面皂,這個(gè)就是上述中的
View部分 - 界面發(fā)生某些操作(比如此時(shí)的+1、-1),需要去更新?tīng)顟B(tài),這就是上述中的
Actions部分
這就是一個(gè)最基本的單頁(yè)面狀態(tài)管理。
2.2 多頁(yè)面狀態(tài)管理
Vue已經(jīng)幫我們做好了單個(gè)界面的狀態(tài)管理,但是如果是多個(gè)界面呢,比如
- 多個(gè)視圖
View都依賴同一個(gè)狀態(tài)(一個(gè)狀態(tài)改了,多個(gè)界面需要進(jìn)行更新) - 不同界面的
Actions都想修改同一個(gè)狀態(tài)
也就是說(shuō)對(duì)于某些狀態(tài)(狀態(tài)1/狀態(tài)2/狀態(tài)3)來(lái)說(shuō)只屬于我們某一個(gè)視圖,但是也有一些狀態(tài)(狀態(tài)a/狀態(tài)b/狀態(tài)c)屬于多個(gè)試圖共同想要維護(hù)的,那怎么辦呢?
- 狀態(tài)1/狀態(tài)2/狀態(tài)3你放在自己的組件中,自己管理自己用,沒(méi)問(wèn)題
- 但是狀態(tài)a/狀態(tài)b/狀態(tài)c我們希望交給一個(gè)大管家來(lái)統(tǒng)一幫助我們管理
沒(méi)錯(cuò),Vuex就是為我們提供這個(gè)大管家的工具。
2.3 全局單例模式
我們現(xiàn)在要做的就是將共享的狀態(tài)抽出來(lái),交給我們的大管家,統(tǒng)一進(jìn)行管理,每個(gè)視圖按照規(guī)定,進(jìn)行訪問(wèn)和修改操作。
這就是Vuex的基本思想
2.4 管理哪些狀態(tài)
如果你做過(guò)大型開(kāi)放,你一定遇到過(guò)多個(gè)狀態(tài),在多個(gè)界面間的共享問(wèn)題。
- 比如用戶的登錄狀態(tài)、用戶名稱、頭像、地理位置信息等
- 比如商品的收藏、購(gòu)物車(chē)中的物品等
這些狀態(tài)信息,我們都可以放在統(tǒng)一放在Vuex中,對(duì)它進(jìn)行保存和管理,而且它們還是響應(yīng)式的。
一般情況下,只有組件之間共享的數(shù)據(jù),才有必要存儲(chǔ)到Vuex中。
對(duì)于組件中的私有數(shù)據(jù),依舊存儲(chǔ)在組件自身的data中即可。
三、Vuex的基本使用
3.1 安裝
npm install vuex --save
3.2 導(dǎo)入
import Vuex from 'vuex'
Vue.use(Vuex)
3.3 創(chuàng)建store對(duì)象
const store = new Vuex.Store({
// state中存放的就是全局共享數(shù)據(jù)
state:{
count: 0
}
})
3.4 掛載store對(duì)象
將創(chuàng)建的共享數(shù)據(jù)對(duì)象store掛載到Vue實(shí)例中,所有的組件,就可以直接從store中獲取全局的數(shù)據(jù)了
new Vue({
el: '#app',
render: h=>h(app)m
router,
store
})
四、Vuex的核心概念
4.1 State
4.1.1 概念
State是提供唯一的公共數(shù)據(jù)源,所有共享的數(shù)據(jù)都要統(tǒng)一放到Store的State中進(jìn)行存儲(chǔ)。
如果狀態(tài)信息是保存到多個(gè)Store對(duì)象中的,那么之后的管理和維護(hù)等都會(huì)變得特別困難,所以Vuex也使用了單一狀態(tài)樹(shù)(單一數(shù)據(jù)源Single Source of Truth)來(lái)管理應(yīng)用層級(jí)的全部狀態(tài)。
單一狀態(tài)樹(shù)能夠讓我們最直接的方式找到某個(gè)狀態(tài)的片段,而且在之后的維護(hù)和調(diào)試過(guò)程中,也可以非常方便的管理和維護(hù)。
export default new Vuex.Store({
state: {
count: 0
},
}
4.1.2 State數(shù)據(jù)訪問(wèn)方式一
通過(guò)this.$store.state.全局?jǐn)?shù)據(jù)名稱訪問(wèn),eg.
<h3>當(dāng)前最新Count值為:{{this.$store.state.count}}</h3>
4.1.3 State數(shù)據(jù)訪問(wèn)方式二
從vuex中按需導(dǎo)入mapState函數(shù)
import { mapState } from 'vuex'
通過(guò)剛才導(dǎo)入的mapState函數(shù),將當(dāng)前組件需要的全局?jǐn)?shù)據(jù),映射為當(dāng)前組件的computed計(jì)算屬性:
<template>
<div>
<h3>當(dāng)前最新Count值為:{{ count }}</h3>
<button>-1</button>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
computed: {
...mapState(["count"])
}
};
</script>
4.2 Mutation
4.2.1 引入
如果想修改count的值,要怎么做呢?
也許聰明的你,已經(jīng)想到,直接在組件中對(duì)this.$store.state.count進(jìn)行操作即可,代碼如下:
<template>
<div>
<h3>當(dāng)前最新Count值為:{{this.$store.state.count}}</h3>
<button @click="add">+1</button>
</div>
</template>
<script>
export default {
methods: {
add() {
this.$store.state.count++;
}
}
};
</script>
測(cè)試發(fā)現(xiàn),這可以實(shí)現(xiàn)需求,完成+1操作。
但是,這種方法在vuex中是嚴(yán)格禁止的,那要怎么做呢?這時(shí),就需要使用Mutation了。
4.2.2 概念
Mutation用于變更存儲(chǔ)在Store中的數(shù)據(jù)。
- 只能通過(guò)
mutation變更Store數(shù)據(jù),不可以直接操作Store中的數(shù)據(jù) - 通過(guò)這種方式,雖然操作稍微繁瑣一些,但可以集中監(jiān)控所有數(shù)據(jù)的變化,二直接操作
Store數(shù)據(jù)是無(wú)法進(jìn)行監(jiān)控的
4.2.3 定義Mutation函數(shù)
在mutations中定義函數(shù),如下:
mutations: {
// 自增
add(state) {
state.count++
}
}
定義的函數(shù)會(huì)有一個(gè)默認(rèn)參數(shù)state,這個(gè)就是存儲(chǔ)在Store中的state對(duì)象。
4.2.4 調(diào)用Mutation函數(shù)
Mutation中不可以執(zhí)行異步操作,如需異步,請(qǐng)?jiān)贏ction中處理
4.2.4.1 方式一
在組件中,通過(guò)this.$store.commit(方法名)完成觸發(fā),如下:
export default {
methods: {
add() {
// this.$store.state.count++;
this.$store.commit("add");
}
}
};
4.2.4.2 方式二
在組件中導(dǎo)入mapMutations函數(shù)
import { mapMutations } from 'vuex'
通過(guò)剛才導(dǎo)入的mapMutations函數(shù),將需要的mutations函數(shù)映射為當(dāng)前組件的methods方法:
methods:{
...mapMutations('add','addN'),
// 當(dāng)前組件設(shè)置的click方法
addCount(){
this.add()
}
}
4.3 Mutation傳遞參數(shù)
在通過(guò)mutation更新數(shù)據(jù)的時(shí)候,有時(shí)候需攜帶一些額外的參數(shù),此處,參數(shù)被成為mutation的載荷Payload。
如果僅有一個(gè)參數(shù)時(shí),那payload對(duì)應(yīng)的就是這個(gè)參數(shù)值,eg.

如果是多參數(shù)的話,那就會(huì)以對(duì)象的形式傳遞,此時(shí)的payload是一個(gè)對(duì)象,可以從對(duì)象中取出相關(guān)的數(shù)據(jù)。
在mutations中定義函數(shù)時(shí),同樣可以接收參數(shù),示例如下:
mutations: {
// 自增
add(state) {
state.count++
},
// 帶參數(shù)
addNum(state, payload) {
state.count += payload.number
}
}
在組件中,調(diào)用如下:
methods: {
add() {
// this.$store.state.count++;
this.$store.commit("add");
},
addNum() {
this.$store.commit("addNum", {
number: 10
});
}
}
4.4 Mutation響應(yīng)規(guī)則
Vuex的store中的State是響應(yīng)式的,當(dāng)State中的數(shù)據(jù)發(fā)生改變時(shí),Vue組件也會(huì)自動(dòng)更新。
這就要求我們必須遵守一些Vuex對(duì)應(yīng)的規(guī)則:
- 提前在
store中初始化好所需的屬性 - 當(dāng)給
State中的對(duì)象添加新屬性時(shí),使用如下方式:- 使用
Vue.set(obj,'newProp','propValue') - 用新對(duì)象給舊對(duì)象重新賦值
- 使用
示例代碼:
updateUserInfo(state) {
// 方式一
Vue.set('user', 'address', '北京市')
// 方式二
state.user = {
...state.user,
'address': '上海市'
}
}
4.5 Mutation常量類(lèi)型
4.5.1 引入
思考一個(gè)問(wèn)題:
在mutation中, 我們定義了很多事件類(lèi)型(也就是其中的方法名稱),當(dāng)項(xiàng)目越來(lái)越大時(shí),Vuex管理的狀態(tài)越來(lái)越多,需要更新?tīng)顟B(tài)的情況也越來(lái)越多,也就意味著Mutation中的方法越來(lái)越多。
當(dāng)方法過(guò)多,使用者需要花費(fèi)大量時(shí)間精力去記住這些方法,甚至多個(gè)文件間來(lái)回切換,查看方法名稱,也存在拷貝或拼寫(xiě)錯(cuò)誤的情況。
那么該如何避免呢?
- 在各種Flux實(shí)現(xiàn)中,一種很常見(jiàn)的方案就是使用常量替代
Mutation事件的類(lèi)型 - 可以將這些常量放在一個(gè)單獨(dú)的文件中,方便管理,整個(gè)
App所有的事件類(lèi)型一目了然
4.5.2 解決方案
解決方案:
- 創(chuàng)建
mutation-types.js文件,在其中定義常量 - 定義常量時(shí), 可以使用
ES2015中的風(fēng)格, 使用一個(gè)常量來(lái)作為函數(shù)的名稱 - 使用處引入文件即可
新建mutation-types.js:
在store/index.js中引入并使用:
import Vue from 'vue'
import Vuex from 'vuex'
import * as types from './mutation-type'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
user: {
name: '旺財(cái)',
age: 12
}
},
mutations: {
// 自增
[types.ADD_NUM](state) {
state.count++
},
}
在組件中,引入并調(diào)用:
<script>
import { ADD_NUM } from "../store/mutation-type";
export default {
methods: {
add() {
this.$store.commit(ADD_NUM);
// this.addAsync();
// this.$store.state.count++;
// this.$store.commit("add");
}
}
};
</script>
4.3 Action
Action類(lèi)似于Mutation,但是是用于處理異步任務(wù)的,比如網(wǎng)絡(luò)請(qǐng)求等
如果通過(guò)異步操作變更數(shù)據(jù),必須通過(guò)Action,而不能使用Mutation,但在Action中還是要通過(guò)觸發(fā)Mutation的方式間接變更數(shù)據(jù)。
4.3.1 參數(shù)context
在actions中定義的方法,都會(huì)有默認(rèn)值context。
-
context是和store對(duì)象具有相同方法和屬性的對(duì)象 - 可以通過(guò)
context進(jìn)行commit相關(guān)操作,可以獲取context.state數(shù)據(jù)
但他們并不是同一個(gè)對(duì)象,在Modules中會(huì)介紹到區(qū)別。
4.3.2 使用方式一
在index.js中,添加actions及對(duì)應(yīng)的方法:
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
// 自增
add(state) {
state.count++
}
},
actions: {
addAsync(context) {
setTimeout(() => {
context.commit('add')
}, 1000);
}
}
})
組件中調(diào)用:
<script>
export default {
methods: {
addNumSync(){
// dispatch用于觸發(fā)Actions中的方法
this.$store.dispatch('addAsync')
}
}
};
</script>
4.3.3 使用方式二
在組件中,導(dǎo)入mapActions函數(shù)
import { mapActions } from 'vuex'
通過(guò)剛才導(dǎo)入的mapActions函數(shù),將需要的actions函數(shù)映射為當(dāng)前組件的methods方法:
<script>
import { mapActions } from "vuex";
export default {
methods: {
...mapActions(["addAsync"]),
add() {?
this.addAsync()
},
}
4.3.4 使用方式三
在導(dǎo)入mapActions后,可以直接將指定方法綁定在@click事件上。
...mapActions(["addAsync"]),
---------------------------
<button @click="addAsync">+1(異步)</button>
該方式也適用于導(dǎo)入的mapMutations
4.3.5 Actions攜帶參數(shù)
在index.js的actions中,增加攜帶參數(shù)方法,如下:
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
// 帶參數(shù)
addNum(state, payload) {
state.count += payload.number
}
},
actions: {
addAsyncParams(context, payload) {
setTimeout(() => {
context.commit('addNum', payload)
}, 1000);
}
}
})
在組件中,調(diào)用如下:
methods: {
addNumSyncParams() {
this.$store.dispatch("addAsyncParams", {
number: 100
});
}
}
4.3.6 Actions與Promise結(jié)合
Promise經(jīng)常用于異步操作,在Action中,可以將異步操作放在Promise中,并且在成功或失敗后,調(diào)用對(duì)應(yīng)的resolve或reject。
示例:
在store/index.js中,為actions添加異步方法:
actions: {
loadUserInfo(context){
return new Promise((resolve)=>{
setTimeout(() => {
context.commit('add')
resolve()
}, 2000);
})
}
}
在組件中調(diào)用,如下:
methods: {
addPromise() {
this.$store.dispatch("loadUserInfo").then(res => {
console.log("done");
});
}
}
4.4 Getter
-
Getters用于對(duì)Store中的數(shù)據(jù)進(jìn)行加工處理形成新的數(shù)據(jù),類(lèi)似于Vue中的計(jì)算屬性 -
Store中數(shù)據(jù)發(fā)生變化,Getters的數(shù)據(jù)也會(huì)跟隨變化
4.4.1 使用方式一
在index.js中定義getters
getters:{
showNum(state){
return '當(dāng)前Count值為:'+state.count
}
}
在組件中使用:
<h3>{{ this.$store.getters.showNum }}</h3>
4.4.2 使用方式二
在組件中,導(dǎo)入mapGetters函數(shù)
import { mapGetters } from 'vuex'
通過(guò)剛才導(dǎo)入的mapGetters函數(shù),將需要的getters函數(shù)映射為當(dāng)前組件的computed方法:
computed: {
...mapGetters(["showNum"])
}
使用時(shí),直接調(diào)用即可:
<h3>{{ showNum }}</h3>
4.5 Modules
4.5.1 概念
Module是模塊的意思,為什么會(huì)在Vuex中使用模塊呢?
-
Vues使用單一狀態(tài)樹(shù),意味著很多狀態(tài)都會(huì)交給Vuex來(lái)管理 - 當(dāng)應(yīng)用變的非常復(fù)雜時(shí),
Store對(duì)象就可能變的相當(dāng)臃腫 - 為解決這個(gè)問(wèn)題,
Vuex允許我們將store分割成模塊(Module),并且每個(gè)模塊擁有自己的State、Mutation、Actions、Getters等
4.5.2 使用
在store目錄下,新建文件夾modules,用于存放各個(gè)模塊的modules文件,此處以moduleA為例。
在modules文件夾中,新建moduleA.js,內(nèi)部各屬性state、mutations等都和之前一致,注釋詳見(jiàn)代碼,示例如下:
export default {
state: {
name: '鳳凰于飛'
},
actions: {
aUpdateName(context) {
setTimeout(() => {
context.commit('updateName', '旺財(cái)')
}, 1000);
}
},
mutations: {
updateName(state, payload) {
state.name = payload
}
},
getters: {
fullName(state) {
return state.name + '王昭君'
},
fullName2(state, getters) {
// 通過(guò)getters調(diào)用本組方法
return getters.fullName + ' 禮拜'
},
fullName3(state, getters, rootState) {
// state代表當(dāng)前module數(shù)據(jù)狀態(tài),rootState代表根節(jié)點(diǎn)數(shù)據(jù)狀態(tài)
return getters.fullName2 + rootState.counter
}
}
}
- 局部狀態(tài)通過(guò)
context.state暴露出來(lái),根節(jié)點(diǎn)狀態(tài)則為context.rootState
在store/index.js中引用moduleA,如下:
import Vue from "vue"
import Vuex from "vuex"
import moduleA from './modules/moduleA'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
a: moduleA
}
})
export default store
這樣就通過(guò)分模塊完成了對(duì)狀態(tài)管理的模塊化拆分。
4.6 優(yōu)化
如果項(xiàng)目非常復(fù)雜,除了分模塊劃分外,還可以將主模塊的actions、mutations、getters等分別獨(dú)立出去,拆分成單獨(dú)的js文件,分別通過(guò)export導(dǎo)出,然后再index.js中導(dǎo)入使用。
示例:
分別將主模塊的actions、mutations、getters獨(dú)立成js文件并導(dǎo)出,以actions.js為例,
export default{
aUpdateInfo(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('updateInfo')
resolve()
}, 1000);
})
}
}
在store/index.js中,引入并使用,如下:
import Vue from "vue"
import Vuex from "vuex"
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
import moduleA from './modules/moduleA'
Vue.use(Vuex)
const state = {
counter: 1000,
students: [
{ id: 1, name: '旺財(cái)', age: 12 },
{ id: 2, name: '小強(qiáng)', age: 31 },
{ id: 3, name: '大明', age: 45 },
{ id: 4, name: '狗蛋', age: 78 }
],
info: {
name: 'keko'
}
}
const store = new Vuex.Store({
state,
mutations,
getters,
actions,
modules: {
a: moduleA
}
})
export default store
最終項(xiàng)目目錄圖:
這樣子,結(jié)構(gòu)清晰明了,也便于后期的維護(hù)。