淺談Vuex注入Vue生命周期的過程
這篇文章是【前端詞典】系列文章的第 13 篇文章,接下的 9 篇我會(huì)圍繞著 Vue 展開,希望這 9 篇文章可以使大家加深對(duì) Vue 的了解。當(dāng)然這些文章的前提是默認(rèn)你對(duì) Vue 有一定的基礎(chǔ)。如果一點(diǎn)基礎(chǔ)都沒有,建議先看官方文檔。
第一篇文章我會(huì)結(jié)合 Vue 和 Vuex 的部分源碼,來說明 Vuex 注入 Vue 生命周期的過程。
說到源碼,其實(shí)沒有想象的那么難。也和我們平時(shí)寫業(yè)務(wù)代碼差不多,都是方法的調(diào)用。但是源碼的調(diào)用樹會(huì)復(fù)雜很多。
為何使用 Vuex
使用 Vue 我們就不可避免的會(huì)遇到組件間共享的數(shù)據(jù)或狀態(tài)。應(yīng)用的業(yè)務(wù)代碼逐漸復(fù)雜,props、事件、事件總線等通信的方式的弊端就會(huì)愈發(fā)明顯。這個(gè)時(shí)候我們就需要 Vuex 。Vuex 是一個(gè)專門為 Vue 設(shè)計(jì)的狀態(tài)管理工具。
狀態(tài)管理是 Vue 組件解耦的重要手段。
它借鑒了 Flux、redux 的基本思想,將狀態(tài)抽離到全局,形成一個(gè) Store。

Vuex 不限制你的代碼結(jié)構(gòu),但需要遵守一些規(guī)則:
- 應(yīng)用層級(jí)的狀態(tài)應(yīng)該集中到單個(gè) store 對(duì)象中
- 提交 mutation 是更改狀態(tài)的唯一方法,并且這個(gè)過程是同步的
- 異步邏輯都應(yīng)該封裝到 action 里面
Vuex 注入 Vue 生命周期的過程
我們?cè)诎惭b插件的時(shí)候,總會(huì)像下面一樣用 Vue.use() 來載入插件,可是 Vue.use() 做了什么呢?
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex);
Vue.use() 做了什么
安裝 Vue.js 插件。如果插件是一個(gè)對(duì)象,必須提供 install 方法。如果插件是一個(gè)函數(shù),它會(huì)被作為 install 方法。install 方法調(diào)用時(shí),會(huì)將 Vue 作為參數(shù)傳入。
以上是官方文檔的解釋。
接下來我們從源碼部分來看看 Vue.use() 都做了什么。
Vue 源碼在 initGlobalAPI 入口方法中調(diào)用了 initUse (Vue) 方法,這個(gè)方法定義了 Vue.use() 需要做的內(nèi)容。
function initGlobalAPI (Vue) {
......
initUse(Vue);
initMixin$1(Vue); // 下面講 Vue.mixin 會(huì)提到
......
}
function initUse (Vue) {
Vue.use = function (plugin) {
var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
/* 判斷過這個(gè)插件是否已經(jīng)安裝 */
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
var args = toArray(arguments, 1);
args.unshift(this);
/* 判斷插件是否有 install 方法 */
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args);
} else if (typeof plugin === 'function') {
plugin.apply(null, args);
}
installedPlugins.push(plugin);
return this
};
}
這段代碼主要做了兩件事情:
- 一件是防止重復(fù)安裝相同的 plugin
- 另一件是初始化 plugin
插件的 install 方法
看完以上源碼,我們知道插件(Vuex)需要提供一個(gè) install 方法。那么我們看看 Vuex 源碼中是否有這個(gè)方法。結(jié)果當(dāng)然是有的:
/* 暴露給外部的 install 方法 */
function install (_Vue) {
/* 避免重復(fù)安裝(Vue.use 內(nèi)部也會(huì)檢測(cè)一次是否重復(fù)安裝同一個(gè)插件)*/
if (Vue && _Vue === Vue) {
{
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
);
}
return
}
Vue = _Vue;
/* 將 vuexInit 混淆進(jìn) Vue 的 beforeCreate(Vue2.0) 或 _init 方法(Vue1.0) */
applyMixin(Vue);
}
這段代碼主要做了兩件事情:
- 一件是防止 Vuex 被重復(fù)安裝
- 另一件是執(zhí)行
applyMixin,目的是執(zhí)行vuexInit方法初始化 Vuex
接下來 我們看看 applyMixin(Vue) 源碼:
/* 將 vuexInit 混淆進(jìn) Vue 的 beforeCreate */
function applyMixin (Vue) {
var version = Number(Vue.version.split('.')[0]);
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit });
} else {
/* Vue1.0 的處理邏輯,此處省略 */
......
}
function vuexInit () {
......
}
}
從上面的源碼,可以看出 Vue.mixin 方法將 vuexInit 方法混淆進(jìn) beforeCreate 鉤子中,也是因?yàn)檫@個(gè)操作,所以每一個(gè) vm 實(shí)例都會(huì)調(diào)用 vuexInit 方法。那么 vuexInit 又做了什么呢?
vuexInit()
我們?cè)谑褂?Vuex 的時(shí)候,需要將 store 傳入到 Vue 實(shí)例中去。
new Vue({
el: '#app',
store
});
但是我們卻在每一個(gè) vm 中都可以訪問該 store,這個(gè)就需要靠 vuexInit 了。
function vuexInit () {
const options = this.$options
if (options.store) {
/* 根節(jié)點(diǎn)存在 stroe 時(shí) */
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
/* 子組件直接從父組件中獲取 $store,這樣就保證了所有組件都公用了全局的同一份 store*/
this.$store = options.parent.$store
}
}
根節(jié)點(diǎn)存在 stroe 時(shí),則直接將 options.store 賦值給 this.$store 。否則則說明不是根節(jié)點(diǎn),從父節(jié)點(diǎn)的 $store 中獲取。
通過這步的操作,我們就以在任意一個(gè) vm 中通過 this.$store 來訪問 Store 的實(shí)例。接下來我們反過來說說 Vue.mixin()。
Vue.mixin()
全局注冊(cè)一個(gè)混入,影響注冊(cè)之后所有創(chuàng)建的每個(gè) Vue 實(shí)例。插件作者可以使用混入,向組件注入自定義的行為。 不推薦在應(yīng)用代碼中使用。
在 vue 的 initGlobalAPI 入口方法中調(diào)用了 initMixin$1(Vue) 方法:
function initMixin$1 (Vue) {
Vue.mixin = function (mixin) {
this.options = mergeOptions(this.options, mixin);
return this
};
}
Vuex 注入 Vue 生命周期的過程大概就是這樣,如果你感興趣的話,你可以直接看看 Vuex 的源碼,接下來我們說說 Store。
Store
上面我們講到了 vuexInit 會(huì)從 options 中獲取 Store。所以接下來會(huì)講到 Store 是怎么來的呢?
我們使用 Vuex 的時(shí)候都會(huì)定義一個(gè)和下面類似的 Store 實(shí)例。
import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
Vue.use(Vuex)
const state = {
showState: 0,
}
export default new Vuex.Store({
strict: true,
state,
getters,
})
不要在發(fā)布環(huán)境下啟用嚴(yán)格模式。嚴(yán)格模式會(huì)深度監(jiān)測(cè)狀態(tài)樹來檢測(cè)不合規(guī)的狀態(tài)變更 —— 請(qǐng)確保在發(fā)布環(huán)境下關(guān)閉嚴(yán)格模式,以避免性能損失。
state 的響應(yīng)式
你是否關(guān)心 state 是如何能夠響應(yīng)式呢?這個(gè)主要是通過 Store 的構(gòu)造函數(shù)中調(diào)用的 resetStoreVM(this, state) 方法來實(shí)現(xiàn)的。
這個(gè)方法主要是重置一個(gè)私有的 _vm(一個(gè) Vue 的實(shí)例) 對(duì)象。這個(gè) _vm 對(duì)象會(huì)保留我們的 state 樹,以及用計(jì)算屬性的方式存儲(chǔ)了 store 的 getters?,F(xiàn)在具體看看它的實(shí)現(xiàn)過程。
/* 使用 Vue 內(nèi)部的響應(yīng)式注冊(cè) state */
function resetStoreVM (store, state, hot) {
/* 存放之前的vm對(duì)象 */
const oldVm = store._vm
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
/* 通過 Object.defineProperty 方法為 store.getters 定義了 get 方法。當(dāng)在組件中調(diào)用 this.$store.getters.xxx 這個(gè)方法的時(shí)候,會(huì)訪問 store._vm[xxx]*/
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
const silent = Vue.config.silent
/* 設(shè)置 silent 為 true 的目的是為了取消 _vm 的所有日志和警告 */
Vue.config.silent = true
/* 這里new了一個(gè)Vue對(duì)象,運(yùn)用Vue內(nèi)部的響應(yīng)式實(shí)現(xiàn)注冊(cè)state以及computed*/
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
/* 使能嚴(yán)格模式,Vuex 中對(duì) state 的修改只能在 mutation 的回調(diào)函數(shù)里 */
if (store.strict) {
enableStrictMode(store)
}
if (oldVm) {
/* 解除舊 vm 的 state 的引用,并銷毀這個(gè)舊的 _vm 對(duì)象 */
if (hot) {
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
state 的響應(yīng)式大概就是這樣實(shí)現(xiàn)的,也就是初始化 resetStoreVM 方法的過程。
看看 Store 的 commit 方法
我們知道 commit 方法是用來觸發(fā) mutation 的。
commit (_type, _payload, _options) {
/* unifyObjectStyle 方法校參 */
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
/* 找到相應(yīng)的 mutation 方法 */
const entry = this._mutations[type]
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
/* 執(zhí)行 mutation 中的方法 */
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
/* 通知所有訂閱者,傳入當(dāng)前的 mutation 對(duì)象和當(dāng)前的 state */
this._subscribers.forEach(sub => sub(mutation, this.state))
if (
process.env.NODE_ENV !== 'production' &&
options && options.silent
) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
該方法先進(jìn)行參數(shù)風(fēng)格校驗(yàn),然后利用 _withCommit 方法執(zhí)行本次批量觸發(fā) mutation 處理函數(shù)。執(zhí)行完成后,通知所有 _subscribers (訂閱函數(shù))本次操作的 mutation 對(duì)象以及當(dāng)前的 state 狀態(tài)。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vuejs使用addEventListener的事件如何觸發(fā)執(zhí)行函數(shù)的this
這篇文章主要介紹了Vuejs使用addEventListener的事件觸發(fā)執(zhí)行函數(shù)的this方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
基于vue2.0動(dòng)態(tài)組件及render詳解
下面小編就為大家分享一篇基于vue2.0動(dòng)態(tài)組件及render詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-03-03
vue-router history模式下的微信分享小結(jié)
本篇文章主要介紹了vue-router history模式下的微信分享小結(jié),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-07-07
vue中render函數(shù)和h函數(shù)以及jsx的使用方式
這篇文章主要介紹了vue中render函數(shù)和h函數(shù)以及jsx的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
解決vue-element-admin中配置跨域出現(xiàn)的問題
這篇文章主要介紹了解決vue-element-admin中配置跨域出現(xiàn)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
淺談vuejs實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)視圖原理
這篇文章主要介紹了淺談vuejs實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)視圖原理,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02

