這篇文章是【前端詞典】系列文章的第 13 篇文章,接下的 9 篇我會圍繞着 Vue 展開,但願這 9 篇文章可使你們加深對 Vue 的瞭解。固然這些文章的前提是默認你對 Vue 有必定的基礎。若是一點基礎都沒有,建議先看官方文檔。html
第一篇文章我會結合 Vue 和 Vuex 的部分源碼,來講明 Vuex 注入 Vue 生命週期的過程。前端
說到源碼,其實沒有想象的那麼難。也和咱們平時寫業務代碼差很少,都是方法的調用。可是源碼的調用樹會複雜不少。vue
使用 Vue 咱們就不可避免的會遇到組件間共享的數據或狀態。應用的業務代碼逐漸複雜,props、事件、事件總線等通訊的方式的弊端就會愈發明顯。這個時候咱們就須要 Vuex 。Vuex 是一個專門爲 Vue 設計的狀態管理工具。ios
狀態管理是 Vue 組件解耦的重要手段。vuex
它借鑑了 Flux、redux 的基本思想,將狀態抽離到全局,造成一個 Store。npm
Vuex 不限制你的代碼結構,但須要遵照一些規則:redux
咱們在安裝插件的時候,總會像下面同樣用 Vue.use()
來載入插件,但是 Vue.use()
作了什麼呢?api
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
複製代碼
安裝 Vue.js 插件。若是插件是一個對象,必須提供 install 方法。若是插件是一個函數,它會被做爲 install 方法。install 方法調用時,會將 Vue 做爲參數傳入。緩存
以上是 官方文檔 的解釋。bash
接下來咱們從源碼部分來看看 Vue.use()
都作了什麼。
Vue 源碼在 initGlobalAPI
入口方法中調用了 initUse (Vue)
方法,這個方法定義了 Vue.use()
須要作的內容。
function initGlobalAPI (Vue) {
......
initUse(Vue);
initMixin$1(Vue); // 下面講 Vue.mixin 會提到
......
}
function initUse (Vue) {
Vue.use = function (plugin) {
var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
/* 判斷過這個插件是否已經安裝 */
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
};
}
複製代碼
這段代碼主要作了兩件事情:
看完以上源碼,咱們知道插件(Vuex)須要提供一個 install
方法。那麼咱們看看 Vuex 源碼中是否有這個方法。結果固然是有的:
/* 暴露給外部的 install 方法 */
function install (_Vue) {
/* 避免重複安裝(Vue.use 內部也會檢測一次是否重複安裝同一個插件)*/
if (Vue && _Vue === Vue) {
{
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
);
}
return
}
Vue = _Vue;
/* 將 vuexInit 混淆進 Vue 的 beforeCreate(Vue2.0) 或 _init 方法(Vue1.0) */
applyMixin(Vue);
}
複製代碼
這段代碼主要作了兩件事情:
applyMixin
,目的是執行 vuexInit
方法初始化 Vuex接下來 咱們看看 applyMixin(Vue)
源碼:
/* 將 vuexInit 混淆進 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
方法混淆進 beforeCreate
鉤子中,也是由於這個操做,因此每個 vm 實例都會調用 vuexInit
方法。那麼 vuexInit
又作了什麼呢?
咱們在使用 Vuex 的時候,須要將 store 傳入到 Vue 實例中去。
new Vue({
el: '#app',
store
});
複製代碼
可是咱們卻在每個 vm 中均可以訪問該 store,這個就須要靠 vuexInit
了。
function vuexInit () {
const options = this.$options
if (options.store) {
/* 根節點存在 stroe 時 */
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
/* 子組件直接從父組件中獲取 $store,這樣就保證了全部組件都公用了全局的同一份 store*/
this.$store = options.parent.$store
}
}
複製代碼
根節點存在 stroe 時,則直接將
options.store
賦值給this.$store
。不然則說明不是根節點,從父節點的$store
中獲取。
經過這步的操做,咱們就以在任意一個 vm 中經過 this.$store 來訪問 Store 的實例。接下來咱們反過來講說 Vue.mixin()。
全局註冊一個混入,影響註冊以後全部建立的每一個 Vue 實例。插件做者可使用混入,向組件注入自定義的行爲。不推薦在應用代碼中使用。
在 vue 的 initGlobalAPI
入口方法中調用了 initMixin$1(Vue)
方法:
function initMixin$1 (Vue) {
Vue.mixin = function (mixin) {
this.options = mergeOptions(this.options, mixin);
return this
};
}
複製代碼
Vuex 注入 Vue 生命週期的過程大概就是這樣,若是你感興趣的話,你能夠直接看看 Vuex 的源碼,接下來咱們說說 Store。
上面咱們講到了 vuexInit
會從 options 中獲取 Store。因此接下來會講到 Store 是怎麼來的呢?
咱們使用 Vuex 的時候都會定義一個和下面相似的 Store 實例。
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,
})
複製代碼
不要在發佈環境下啓用嚴格模式。嚴格模式會深度監測狀態樹來檢測不合規的狀態變動 —— 請確保在發佈環境下關閉嚴格模式,以免性能損失。
你是否關心 state 是如何可以響應式呢?這個主要是經過 Store 的構造函數中調用的 resetStoreVM(this, state)
方法來實現的。
這個方法主要是重置一個私有的 _vm(一個 Vue 的實例) 對象。這個 _vm 對象會保留咱們的 state 樹,以及用計算屬性的方式存儲了 store 的 getters。如今具體看看它的實現過程。
/* 使用 Vue 內部的響應式註冊 state */
function resetStoreVM (store, state, hot) {
/* 存放以前的vm對象 */
const oldVm = store._vm
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
/* 經過 Object.defineProperty 方法爲 store.getters 定義了 get 方法。當在組件中調用 this.$store.getters.xxx 這個方法的時候,會訪問 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
/* 設置 silent 爲 true 的目的是爲了取消 _vm 的全部日誌和警告 */
Vue.config.silent = true
/* 這裏new了一個Vue對象,運用Vue內部的響應式實現註冊state以及computed*/
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
/* 使能嚴格模式,Vuex 中對 state 的修改只能在 mutation 的回調函數裏 */
if (store.strict) {
enableStrictMode(store)
}
if (oldVm) {
/* 解除舊 vm 的 state 的引用,並銷燬這個舊的 _vm 對象 */
if (hot) {
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
複製代碼
state 的響應式大概就是這樣實現的,也就是初始化 resetStoreVM 方法的過程。
咱們知道 commit 方法是用來觸發 mutation 的。
commit (_type, _payload, _options) {
/* unifyObjectStyle 方法校參 */
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
/* 找到相應的 mutation 方法 */
const entry = this._mutations[type]
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
/* 執行 mutation 中的方法 */
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
/* 通知全部訂閱者,傳入當前的 mutation 對象和當前的 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'
)
}
}
複製代碼
該方法先進行參數風格校驗,而後利用 _withCommit
方法執行本次批量觸發 mutation
處理函數。執行完成後,通知全部 _subscribers
(訂閱函數)本次操做的 mutation
對象以及當前的 state
狀態。
最近總有朋友問我 Vue 相關的問題,所以接下來我會輸出 9 篇 Vue 相關的文章,但願對你們有必定的幫助。我會保持在 7 到 10 天更新一篇。
建議你關注個人公衆號,第一時間就能夠接收最新的文章。
若是你想加羣交流,也能夠添加有點智能的機器人,自動拉你進羣: