您好,登錄后才能下訂單哦!
這篇文章是【前端詞典】系列文章的第 13 篇文章,接下的 9 篇我會圍繞著 Vue 展開,希望這 9 篇文章可以使大家加深對 Vue 的了解。當然這些文章的前提是默認你對 Vue 有一定的基礎。如果一點基礎都沒有,建議先看官方文檔。
第一篇文章我會結合 Vue 和 Vuex 的部分源碼,來說明 Vuex 注入 Vue 生命周期的過程。
說到源碼,其實沒有想象的那么難。也和我們平時寫業務代碼差不多,都是方法的調用。但是源碼的調用樹會復雜很多。
為何使用 Vuex
使用 Vue 我們就不可避免的會遇到組件間共享的數據或狀態。應用的業務代碼逐漸復雜,props、事件、事件總線等通信的方式的弊端就會愈發明顯。這個時候我們就需要 Vuex 。Vuex 是一個專門為 Vue 設計的狀態管理工具。
狀態管理是 Vue 組件解耦的重要手段。
它借鑒了 Flux、redux 的基本思想,將狀態抽離到全局,形成一個 Store。
Vuex 不限制你的代碼結構,但需要遵守一些規則:
Vuex 注入 Vue 生命周期的過程
我們在安裝插件的時候,總會像下面一樣用 Vue.use()
來載入插件,可是 Vue.use()
做了什么呢?
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex);
Vue.use() 做了什么
安裝 Vue.js 插件。如果插件是一個對象,必須提供 install 方法。如果插件是一個函數,它會被作為 install 方法。install 方法調用時,會將 Vue 作為參數傳入。
以上是官方文檔的解釋。
接下來我們從源碼部分來看看 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 }; }
這段代碼主要做了兩件事情:
插件的 install 方法
看完以上源碼,我們知道插件(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
又做了什么呢?
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.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。
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 的響應式
你是否關心 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 方法的過程。
看看 Store 的 commit 方法
我們知道 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
狀態。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。