文章目錄
一冗尤、Vuex概述
1.1 官方解釋
Vuex
是一個專為 Vue.js
應(yīng)用程序開發(fā)的狀態(tài)管理模式纱烘。
- 它采用集中式存儲管理 應(yīng)用的所有組件的狀態(tài)雳灵,并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化
-Vuex
也集成到Vue
的官方調(diào)試工具devtools extension
,提供了諸如零配置的time-travel
調(diào)試糊肠、狀態(tài)快照導(dǎo)入導(dǎo)出等高級調(diào)試功能辨宠。
1.2 大白話
狀態(tài)管理模式、集中式存儲管理這些名詞聽起來就非常高大上货裹,讓人捉摸不透嗤形。
其實,可以簡單的將其看成把需要多個組件共享的變量全部存儲在一個對象里面弧圆。
然后赋兵,將這個對象放在頂層的Vue
實例中,讓其他組件可以使用搔预。
那么毡惜,多個組件是不是就可以共享這個對象中的所有變量屬性了呢?
如果是這樣的話斯撮,為什么官方還要專門出一個插件Vuex
呢?難道我們不能自己封裝一個對象來管理嗎扶叉?
當(dāng)然可以勿锅,只是我們要先想想VueJS
帶給我們最大的便利是什么呢帕膜?沒錯,就是響應(yīng)式溢十。
如果你自己封裝實現(xiàn)一個對象能不能保證它里面所有的屬性做到響應(yīng)式呢垮刹?當(dāng)然也可以,只是自己封裝可能稍微麻煩一些张弛。
不用懷疑荒典,Vuex
就是為了提供這樣一個在多個組件間共享狀態(tài)的插件,用它就可以了吞鸭。
1.3 組件間共享數(shù)據(jù)的方式
- 父向子傳值:
v-bind
屬性綁定 - 子向父傳值:
v-on
事件綁定 - 兄弟組件之間共享數(shù)據(jù):
EventBus
-
$on
接收數(shù)據(jù)的組件 -
$emit
發(fā)送數(shù)據(jù)的組件
-
上述只適合小范圍內(nèi)數(shù)據(jù)共享寺董,如果是復(fù)雜應(yīng)用的話,就不再合適了刻剥。
1.4 再看Vuex是什么
Vuex
是實現(xiàn)組件全局狀態(tài)(數(shù)據(jù))管理的一種機制遮咖,可以方便的實現(xiàn)組件之間數(shù)據(jù)的共享
如圖:
在不使用Vuex
進行狀態(tài)管理時,如果要從最下面的紫色組件傳遞數(shù)據(jù)的話造虏,還是比較繁瑣御吞,也不便于維護。
在使用Vuex進行狀態(tài)管理時漓藕,只需要一個共享Store
組件陶珠,紫色組件將數(shù)據(jù)寫入Store
中,其他使用的組件直接從Store
中讀取即可享钞。
1.5 使用Vuex統(tǒng)一管理好處
- 能夠在
Vuex
中集中管理共享的數(shù)據(jù)揍诽,易于開發(fā)和后期維護 - 能夠高效地實現(xiàn)組件之間的數(shù)據(jù)共享,提高開發(fā)效率
- 存儲在
Vuex
中的數(shù)據(jù)都是響應(yīng)式的嫩与,能夠?qū)崟r保持數(shù)據(jù)與頁面的同步
二寝姿、狀態(tài)管理
2.1 單頁面狀態(tài)管理
我們知道,要在單個組件中進行狀態(tài)管理是一件非常簡單的事情划滋,如圖
- State:指的就是我們的狀態(tài)饵筑,可以暫時理解為組件中
data
中的屬性 - View:視圖層,可以針對
State
的變化处坪, 顯示不同的信息 - Actions:這里的
Actions
主要是用戶的各種操作根资,如點擊、輸入等同窘,會導(dǎo)致狀態(tài)發(fā)生變化
簡單加減法案例玄帕,代碼如下:
<template>
<div>
<div>當(dāng)前計數(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>
- 在這個案例中,有沒有狀態(tài)需要管理呢想邦?肯定是有的裤纹,就是個數(shù)
counter
-
counter
需要某種方式被記錄下來,也就是上述中的的State
部分 - counter的值需要被顯示在潔面皂,這個就是上述中的
View
部分 - 界面發(fā)生某些操作(比如此時的+1鹰椒、-1)锡移,需要去更新狀態(tài),這就是上述中的
Actions
部分
這就是一個最基本的單頁面狀態(tài)管理漆际。
2.2 多頁面狀態(tài)管理
Vue
已經(jīng)幫我們做好了單個界面的狀態(tài)管理淆珊,但是如果是多個界面呢,比如
- 多個視圖
View
都依賴同一個狀態(tài)(一個狀態(tài)改了奸汇,多個界面需要進行更新) - 不同界面的
Actions
都想修改同一個狀態(tài)
也就是說對于某些狀態(tài)(狀態(tài)1/狀態(tài)2/狀態(tài)3)來說只屬于我們某一個視圖施符,但是也有一些狀態(tài)(狀態(tài)a/狀態(tài)b/狀態(tài)c)屬于多個試圖共同想要維護的,那怎么辦呢擂找?
- 狀態(tài)1/狀態(tài)2/狀態(tài)3你放在自己的組件中戳吝,自己管理自己用,沒問題
- 但是狀態(tài)a/狀態(tài)b/狀態(tài)c我們希望交給一個大管家來統(tǒng)一幫助我們管理
沒錯婴洼,Vuex
就是為我們提供這個大管家的工具骨坑。
2.3 全局單例模式
我們現(xiàn)在要做的就是將共享的狀態(tài)抽出來,交給我們的大管家柬采,統(tǒng)一進行管理欢唾,每個視圖按照規(guī)定,進行訪問和修改操作粉捻。
這就是Vuex
的基本思想
2.4 管理哪些狀態(tài)
如果你做過大型開放礁遣,你一定遇到過多個狀態(tài),在多個界面間的共享問題肩刃。
- 比如用戶的登錄狀態(tài)祟霍、用戶名稱、頭像盈包、地理位置信息等
- 比如商品的收藏沸呐、購物車中的物品等
這些狀態(tài)信息,我們都可以放在統(tǒng)一放在Vuex
中呢燥,對它進行保存和管理崭添,而且它們還是響應(yīng)式的。
一般情況下叛氨,只有組件之間共享的數(shù)據(jù)呼渣,才有必要存儲到Vuex中。
對于組件中的私有數(shù)據(jù)寞埠,依舊存儲在組件自身的data中即可屁置。
三、Vuex的基本使用
3.1 安裝
npm install vuex --save
3.2 導(dǎo)入
import Vuex from 'vuex'
Vue.use(Vuex)
3.3 創(chuàng)建store對象
const store = new Vuex.Store({
// state中存放的就是全局共享數(shù)據(jù)
state:{
count: 0
}
})
3.4 掛載store對象
將創(chuàng)建的共享數(shù)據(jù)對象store
掛載到Vue
實例中仁连,所有的組件蓝角,就可以直接從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
中進行存儲使鹅。
如果狀態(tài)信息是保存到多個Store
對象中的颇象,那么之后的管理和維護等都會變得特別困難,所以Vuex
也使用了單一狀態(tài)樹(單一數(shù)據(jù)源Single Source of Truth
)來管理應(yīng)用層級的全部狀態(tài)响疚。
單一狀態(tài)樹能夠讓我們最直接的方式找到某個狀態(tài)的片段敲茄,而且在之后的維護和調(diào)試過程中,也可以非常方便的管理和維護。
export default new Vuex.Store({
state: {
count: 0
},
}
4.1.2 State數(shù)據(jù)訪問方式一
通過this.$store.state.全局數(shù)據(jù)名稱
訪問拾徙,eg.
<h3>當(dāng)前最新Count值為:{{this.$store.state.count}}</h3>
4.1.3 State數(shù)據(jù)訪問方式二
從vuex
中按需導(dǎo)入mapState
函數(shù)
import { mapState } from 'vuex'
通過剛才導(dǎo)入的mapState
函數(shù),將當(dāng)前組件需要的全局數(shù)據(jù)因惭,映射為當(dāng)前組件的computed
計算屬性:
<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)想到声畏,直接在組件中對this.$store.state.count
進行操作即可,代碼如下:
<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>
測試發(fā)現(xiàn)姻成,這可以實現(xiàn)需求插龄,完成+1
操作。
但是科展,這種方法在vuex
中是嚴格禁止的均牢,那要怎么做呢?這時才睹,就需要使用Mutation
了徘跪。
4.2.2 概念
Mutation
用于變更存儲在Store
中的數(shù)據(jù)。
- 只能通過
mutation
變更Store
數(shù)據(jù)琅攘,不可以直接操作Store
中的數(shù)據(jù) - 通過這種方式垮庐,雖然操作稍微繁瑣一些,但可以集中監(jiān)控所有數(shù)據(jù)的變化坞琴,二直接操作
Store
數(shù)據(jù)是無法進行監(jiān)控的
4.2.3 定義Mutation函數(shù)
在mutations
中定義函數(shù)哨查,如下:
mutations: {
// 自增
add(state) {
state.count++
}
}
定義的函數(shù)會有一個默認參數(shù)state
,這個就是存儲在Store
中的state
對象置济。
4.2.4 調(diào)用Mutation函數(shù)
Mutation中不可以執(zhí)行異步操作解恰,如需異步,請在Action中處理
4.2.4.1 方式一
在組件中浙于,通過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'
通過剛才導(dǎo)入的mapMutations
函數(shù),將需要的mutations
函數(shù)映射為當(dāng)前組件的methods
方法:
methods:{
...mapMutations('add','addN'),
// 當(dāng)前組件設(shè)置的click方法
addCount(){
this.add()
}
}
4.3 Mutation傳遞參數(shù)
在通過mutation
更新數(shù)據(jù)的時候羞酗,有時候需攜帶一些額外的參數(shù)腐宋,此處,參數(shù)被成為mutation
的載荷Payload
。
如果僅有一個參數(shù)時胸竞,那payload
對應(yīng)的就是這個參數(shù)值欺嗤,eg.
如果是多參數(shù)的話,那就會以對象的形式傳遞卫枝,此時的payload
是一個對象煎饼,可以從對象中取出相關(guān)的數(shù)據(jù)。
在mutations
中定義函數(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ā)生改變時沾乘,Vue
組件也會自動更新。
這就要求我們必須遵守一些Vuex
對應(yīng)的規(guī)則:
- 提前在
store
中初始化好所需的屬性 - 當(dāng)給
State
中的對象添加新屬性時浑测,使用如下方式:- 使用
Vue.set(obj,'newProp','propValue')
- 用新對象給舊對象重新賦值
- 使用
示例代碼:
updateUserInfo(state) {
// 方式一
Vue.set('user', 'address', '北京市')
// 方式二
state.user = {
...state.user,
'address': '上海市'
}
}
4.5 Mutation常量類型
4.5.1 引入
思考一個問題:
在mutation
中, 我們定義了很多事件類型(也就是其中的方法名稱)翅阵,當(dāng)項目越來越大時,Vuex
管理的狀態(tài)越來越多迁央,需要更新狀態(tài)的情況也越來越多掷匠,也就意味著Mutation
中的方法越來越多。
當(dāng)方法過多漱贱,使用者需要花費大量時間精力去記住這些方法槐雾,甚至多個文件間來回切換,查看方法名稱幅狮,也存在拷貝或拼寫錯誤的情況募强。
那么該如何避免呢?
- 在各種Flux實現(xiàn)中崇摄,一種很常見的方案就是使用常量替代
Mutation
事件的類型 - 可以將這些常量放在一個單獨的文件中擎值,方便管理,整個
App
所有的事件類型一目了然
4.5.2 解決方案
解決方案:
- 創(chuàng)建
mutation-types.js
文件逐抑,在其中定義常量 - 定義常量時, 可以使用
ES2015
中的風(fēng)格, 使用一個常量來作為函數(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: '旺財',
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
類似于Mutation
,但是是用于處理異步任務(wù)的厕氨,比如網(wǎng)絡(luò)請求等
如果通過異步操作變更數(shù)據(jù)进每,必須通過Action
,而不能使用Mutation
命斧,但在Action
中還是要通過觸發(fā)Mutation
的方式間接變更數(shù)據(jù)田晚。
4.3.1 參數(shù)context
在actions
中定義的方法,都會有默認值context
国葬。
-
context
是和store
對象具有相同方法和屬性的對象 - 可以通過
context
進行commit
相關(guān)操作贤徒,可以獲取context.state
數(shù)據(jù)
但他們并不是同一個對象芹壕,在Modules
中會介紹到區(qū)別。
4.3.2 使用方式一
在index.js
中接奈,添加actions
及對應(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'
通過剛才導(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)用對應(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
用于對Store
中的數(shù)據(jù)進行加工處理形成新的數(shù)據(jù)壤躲,類似于Vue
中的計算屬性 -
Store
中數(shù)據(jù)發(fā)生變化,Getters
的數(shù)據(jù)也會跟隨變化
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'
通過剛才導(dǎo)入的mapGetters
函數(shù)碉克,將需要的getters
函數(shù)映射為當(dāng)前組件的computed
方法:
computed: {
...mapGetters(["showNum"])
}
使用時,直接調(diào)用即可:
<h3>{{ showNum }}</h3>
4.5 Modules
4.5.1 概念
Module
是模塊的意思并齐,為什么會在Vuex
中使用模塊呢漏麦?
-
Vues
使用單一狀態(tài)樹,意味著很多狀態(tài)都會交給Vuex
來管理 - 當(dāng)應(yīng)用變的非常復(fù)雜時况褪,
Store
對象就可能變的相當(dāng)臃腫 - 為解決這個問題撕贞,
Vuex
允許我們將store
分割成模塊(Module)
,并且每個模塊擁有自己的State测垛、Mutation捏膨、Actions、Getters
等
4.5.2 使用
在store
目錄下食侮,新建文件夾modules
号涯,用于存放各個模塊的modules
文件,此處以moduleA
為例锯七。
在modules
文件夾中链快,新建moduleA.js
,內(nèi)部各屬性state
起胰、mutations
等都和之前一致久又,注釋詳見代碼巫延,示例如下:
export default {
state: {
name: '鳳凰于飛'
},
actions: {
aUpdateName(context) {
setTimeout(() => {
context.commit('updateName', '旺財')
}, 1000);
}
},
mutations: {
updateName(state, payload) {
state.name = payload
}
},
getters: {
fullName(state) {
return state.name + '王昭君'
},
fullName2(state, getters) {
// 通過getters調(diào)用本組方法
return getters.fullName + ' 禮拜'
},
fullName3(state, getters, rootState) {
// state代表當(dāng)前module數(shù)據(jù)狀態(tài),rootState代表根節(jié)點數(shù)據(jù)狀態(tài)
return getters.fullName2 + rootState.counter
}
}
}
- 局部狀態(tài)通過
context.state
暴露出來地消,根節(jié)點狀態(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
這樣就通過分模塊完成了對狀態(tài)管理的模塊化拆分。
4.6 優(yōu)化
如果項目非常復(fù)雜脉执,除了分模塊劃分外疼阔,還可以將主模塊的actions
、mutations
半夷、getters
等分別獨立出去婆廊,拆分成單獨的js
文件,分別通過export
導(dǎo)出巫橄,然后再index.js
中導(dǎo)入使用淘邻。
示例:
分別將主模塊的actions
、mutations
湘换、getters
獨立成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: '旺財', age: 12 },
{ id: 2, name: '小強', 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
最終項目目錄圖:
這樣子,結(jié)構(gòu)清晰明了帆离,也便于后期的維護蔬蕊。