您好,登錄后才能下訂單哦!
這篇文章是【前端詞典】系列文章的第 13 篇文章,接下的 9 篇我會(huì)圍繞著 Vue 展開(kāi),希望這 9 篇文章可以使大家加深對(duì) Vue 的了解。當(dāng)然這些文章的前提是默認(rèn)你對(duì) Vue 有一定的基礎(chǔ)。如果一點(diǎn)基礎(chǔ)都沒(méi)有,建議先看官方文檔。
第一篇文章我會(huì)結(jié)合 Vue 和 Vuex 的部分源碼,來(lái)說(shuō)明 Vuex 注入 Vue 生命周期的過(guò)程。
說(shuō)到源碼,其實(shí)沒(méi)有想象的那么難。也和我們平時(shí)寫(xiě)業(yè)務(wù)代碼差不多,都是方法的調(diào)用。但是源碼的調(diào)用樹(shù)會(huì)復(fù)雜很多。
為何使用 Vuex
使用 Vue 我們就不可避免的會(huì)遇到組件間共享的數(shù)據(jù)或狀態(tài)。應(yīng)用的業(yè)務(wù)代碼逐漸復(fù)雜,props、事件、事件總線等通信的方式的弊端就會(huì)愈發(fā)明顯。這個(gè)時(shí)候我們就需要 Vuex 。Vuex 是一個(gè)專門(mén)為 Vue 設(shè)計(jì)的狀態(tài)管理工具。
狀態(tài)管理是 Vue 組件解耦的重要手段。
它借鑒了 Flux、redux 的基本思想,將狀態(tài)抽離到全局,形成一個(gè) Store。
Vuex 不限制你的代碼結(jié)構(gòu),但需要遵守一些規(guī)則:
Vuex 注入 Vue 生命周期的過(guò)程
我們?cè)诎惭b插件的時(shí)候,總會(huì)像下面一樣用 Vue.use()
來(lái)載入插件,可是 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ù)傳入。
以上是官方文檔的解釋。
接下來(lái)我們從源碼部分來(lái)看看 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 = [])); /* 判斷過(guò)這個(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 }; }
這段代碼主要做了兩件事情:
插件的 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); }
這段代碼主要做了兩件事情:
applyMixin
,目的是執(zhí)行 vuexInit
方法初始化 Vuex接下來(lái) 我們看看 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 中都可以訪問(wèn)該 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
。否則則說(shuō)明不是根節(jié)點(diǎn),從父節(jié)點(diǎn)的 $store
中獲取。
通過(guò)這步的操作,我們就以在任意一個(gè) vm 中通過(guò) this.$store 來(lái)訪問(wèn) Store 的實(shí)例。接下來(lái)我們反過(guò)來(lái)說(shuō)說(shuō) 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 生命周期的過(guò)程大概就是這樣,如果你感興趣的話,你可以直接看看 Vuex 的源碼,接下來(lái)我們說(shuō)說(shuō) Store。
Store
上面我們講到了 vuexInit
會(huì)從 options 中獲取 Store。所以接下來(lái)會(huì)講到 Store 是怎么來(lái)的呢?
我們使用 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)樹(shù)來(lái)檢測(cè)不合規(guī)的狀態(tài)變更 —— 請(qǐng)確保在發(fā)布環(huán)境下關(guān)閉嚴(yán)格模式,以避免性能損失。
state 的響應(yīng)式
你是否關(guān)心 state 是如何能夠響應(yīng)式呢?這個(gè)主要是通過(guò) Store 的構(gòu)造函數(shù)中調(diào)用的 resetStoreVM(this, state)
方法來(lái)實(shí)現(xiàn)的。
這個(gè)方法主要是重置一個(gè)私有的 _vm(一個(gè) Vue 的實(shí)例) 對(duì)象。這個(gè) _vm 對(duì)象會(huì)保留我們的 state 樹(shù),以及用計(jì)算屬性的方式存儲(chǔ)了 store 的 getters。現(xiàn)在具體看看它的實(shí)現(xiàn)過(guò)程。
/* 使用 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 = {} /* 通過(guò) Object.defineProperty 方法為 store.getters 定義了 get 方法。當(dāng)在組件中調(diào)用 this.$store.getters.xxx 這個(gè)方法的時(shí)候,會(huì)訪問(wèn) 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 方法的過(guò)程。
看看 Store 的 commit 方法
我們知道 commit 方法是用來(lái)觸發(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)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。