Vuex 狀態(tài)管理的工作原理
為什么要使用 Vuex
當(dāng)我們使用 Vue.js 來(lái)開(kāi)發(fā)一個(gè)單頁(yè)應(yīng)用時(shí),經(jīng)常會(huì)遇到一些組件間共享的數(shù)據(jù)或狀態(tài)敏簿,或是需要通過(guò) props 深層傳遞的一些數(shù)據(jù)扇调。在應(yīng)用規(guī)模較小的時(shí)候擂送,我們會(huì)使用 props弄贿、事件等常用的父子組件的組件間通信方法液斜,或者是通過(guò)事件總線來(lái)進(jìn)行任意兩個(gè)組件的通信求类。但是當(dāng)應(yīng)用逐漸復(fù)雜后奔垦,問(wèn)題就開(kāi)始出現(xiàn)了,這樣的通信方式會(huì)導(dǎo)致數(shù)據(jù)流異常地混亂。
這個(gè)時(shí)候,我們就需要用到我們的狀態(tài)管理工具 Vuex 了啼染。Vuex 是一個(gè)專門為 Vue.js 框架設(shè)計(jì)的够傍、專門用來(lái)對(duì)于 Vue.js 應(yīng)用進(jìn)行狀態(tài)管理的庫(kù)。它借鑒了 Flux悦析、redux 的基本思想,將狀態(tài)抽離到全局,形成一個(gè) Store筐咧。因?yàn)?Vuex 內(nèi)部采用了 new Vue 來(lái)將 Store 內(nèi)的數(shù)據(jù)進(jìn)行「響應(yīng)式化」,所以 Vuex 是一款利用 Vue 內(nèi)部機(jī)制的庫(kù)噪矛,與 Vue 高度契合量蕊,與 Vue 搭配使用顯得更加簡(jiǎn)單高效,但缺點(diǎn)是不能與其他的框架(如 react)配合使用摩疑。
本節(jié)將簡(jiǎn)單介紹 Vuex 最核心的內(nèi)部機(jī)制危融,起個(gè)拋磚引玉的作用,想了解更多細(xì)節(jié)可以參考筆者 Github 上的另一篇文章 《Vuex源碼解析》或者直接閱讀 Vuex源碼雷袋。
安裝
Vue.js 提供了一個(gè) Vue.use
的方法來(lái)安裝插件吉殃,內(nèi)部會(huì)調(diào)用插件提供的 install
方法辞居。
Vue.use(Vuex);
所以我們的插件需要提供一個(gè) install
方法來(lái)安裝。
let Vue;
export default install (_Vue) {
Vue.mixin({ beforeCreate: vuexInit });
Vue = _Vue;
}
我們采用 Vue.mixin
方法將 vuexInit
方法混淆進(jìn) beforeCreate
鉤子中蛋勺,并用 Vue
保存 Vue 對(duì)象瓦灶。那么 vuexInit
究竟實(shí)現(xiàn)了什么呢?
我們知道抱完,在使用 Vuex 的時(shí)候贼陶,我們需要將 store
傳入到 Vue 實(shí)例中去。
/*將store放入Vue創(chuàng)建時(shí)的option中*/
new Vue({
el: '#app',
store
});
但是我們卻在每一個(gè) vm 中都可以訪問(wèn)該 store
巧娱,這個(gè)就需要靠 vuexInit
了碉怔。
function vuexInit () {
const options = this.$options;
if (options.store) {
this.$store = options.store;
} else {
this.$store = options.parent.$store;
}
}
因?yàn)橹耙呀?jīng)用Vue.mixin
方法將 vuexInit
方法混淆進(jìn) beforeCreate
鉤子中,所以每一個(gè) vm 實(shí)例都會(huì)調(diào)用 vuexInit
方法禁添。
如果是根節(jié)點(diǎn)($options
中存在 store
說(shuō)明是根節(jié)點(diǎn))撮胧,則直接將 options.store
賦值給 this.$store
。否則則說(shuō)明不是根節(jié)點(diǎn)老翘,從父節(jié)點(diǎn)的 $store
中獲取芹啥。
通過(guò)這步的操作,我們已經(jīng)可以在任意一個(gè) vm 中通過(guò) this.$store
來(lái)訪問(wèn) Store
的實(shí)例啦~
Store
數(shù)據(jù)的響應(yīng)式化
首先我們需要在 Store
的構(gòu)造函數(shù)中對(duì) state
進(jìn)行「響應(yīng)式化」铺峭。
constructor () {
this._vm = new Vue({
data: {
$$state: this.state
}
})
}
熟悉「響應(yīng)式」的同學(xué)肯定知道墓怀,這個(gè)步驟以后,state
會(huì)將需要的依賴收集在 Dep
中卫键,在被修改時(shí)更新對(duì)應(yīng)視圖傀履。我們來(lái)看一個(gè)小例子。
let globalData = {
d: 'hello world'
};
new Vue({
data () {
return {
$$state: {
globalData
}
}
}
});
/* modify */
setTimeout(() => {
globalData.d = 'hi~';
}, 1000);
Vue.prototype.globalData = globalData;
任意模板中
<div>{{globalData.d}}</div>
上述代碼在全局有一個(gè) globalData
永罚,它被傳入一個(gè) Vue
對(duì)象的 data
中啤呼,之后在任意 Vue 模板中對(duì)該變量進(jìn)行展示,因?yàn)榇藭r(shí) globalData
已經(jīng)在 Vue 的 prototype
上了所以直接通過(guò) this.prototype
訪問(wèn)呢袱,也就是在模板中的 {{globalData.d}}
官扣。此時(shí),setTimeout
在 1s 之后將 globalData.d
進(jìn)行修改羞福,我們發(fā)現(xiàn)模板中的 globalData.d
發(fā)生了變化惕蹄。其實(shí)上述部分就是 Vuex 依賴 Vue 核心實(shí)現(xiàn)數(shù)據(jù)的“響應(yīng)式化”。
講完了 Vuex 最核心的通過(guò) Vue 進(jìn)行數(shù)據(jù)的「響應(yīng)式化」治专,接下來(lái)我們?cè)賮?lái)介紹兩個(gè) Store
的 API卖陵。
commit
首先是 commit
方法,我們知道 commit
方法是用來(lái)觸發(fā) mutation
的张峰。
commit (type, payload, _options) {
const entry = this._mutations[type];
entry.forEach(function commitIterator (handler) {
handler(payload);
});
}
從 _mutations
中取出對(duì)應(yīng)的 mutation泪蔫,循環(huán)執(zhí)行其中的每一個(gè) mutation。
dispatch
dispatch
同樣道理喘批,用于觸發(fā) action撩荣,可以包含異步狀態(tài)铣揉。
dispatch (type, payload) {
const entry = this._actions[type];
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload);
}
同樣的,取出 _actions
中的所有對(duì)應(yīng) action餐曹,將其執(zhí)行逛拱,如果有多個(gè)則用 Promise.all
進(jìn)行包裝。
最后
理解 Vuex 的核心在于理解其如何與 Vue 本身結(jié)合台猴,如何利用 Vue 的響應(yīng)式機(jī)制來(lái)實(shí)現(xiàn)核心 Store 的「響應(yīng)式化」朽合。
Vuex 本身代碼不多且設(shè)計(jì)優(yōu)雅,非常值得一讀饱狂,想閱讀源碼的同學(xué)請(qǐng)看Vuex源碼曹步。
注:本節(jié)代碼參考《Vuex狀態(tài)管理的工作原理》。