Vuex源碼閱讀(一) ,介紹Vuex的執行順序,以及new Vuex.Store()的時候內部都幹了什麼。 ...
1. 前言
Vuex版本:3.4.0
Vuex倉庫:https://github.com/vuejs/vuex
Vux文檔:https://vuex.vuejs.org/zh/guide/
文章時間:2020-06-09
2. 執行順序
首先看個簡單的代碼塊:
// store.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); let baseStore = new Vuex.Store({ state: { count: 0 }, }); export default baseStore; // app.js import Vue from 'vue' import store from './store' new Vue({ el: '#app', store })
2.1 第一步:Vue.use(Vuex)
說明:這一步是Vuex在Vue的beforeCreate事件內增加一個回調函數,其目的是為把初始化後的store對象掛載到this.$store,即Vue.$store。
代碼:
Vue.mixin({ beforeCreate: vuexInit }); function vuexInit() { const options = this.$options; // store injection store註入 if (options.store) { this.$store = typeof options.store === 'function' ? options.store() : options.store; } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store; } }
2.2 第二步:new Vuex.Store({})
說明:初始化具體的store對象。
2.3 第三步:new Vue({ store })
說明:這裡主要是為了執行第一步的代碼,因為第一步的Vue.use(Vuex)只是註入一個回調,內部的條件判斷options.store 和 options.parent && options.parent.$store都沒有生效,只有在這一步時才會生效,其目的就向上面說的把初始化後的store對象掛載到this.$store,這樣所有子組件就可以通過this.$store調用store對象。
代碼:
new Vue({ el: '#app', router, components: { App }, store: { baseStore: baseStore }, template: '<App/>' });
3. 探究new Vuex.Store({})
說明:這裡將著重介紹new Vuex.Store({})都幹了什麼。
註意:此處的講解都是以使用單一狀態樹為前提條件,沒有Module以及Module內的namespaced等知識點,modules這塊會單獨講解。
3.1 重新綁定dispatch、commit
說明:此處重新綁定dispatch、commit方法,把store自身插入到第一個參數前面。
這也是為什麼我們在外部調用this.$store.dispatch('actionName')時,所創建的action第一個參數為store本身。
代碼:
this.dispatch = function boundDispatch (type, payload) { return dispatch.call(store, type, payload) } this.commit = function boundCommit (type, payload, options) { return commit.call(store, type, payload, options) }
3.2 轉換為Module對象
說明:在這裡Vuex將傳入的Vuex代碼解析為Model對象(後面將以options表示傳入的代碼塊):
new Vuex.Store({ state: { count: 0 }, getters, actions, mutations });
在Vuex源碼中,會將options轉換為Module集合:
代碼:
// src/store.js
this._modules = new ModuleCollection(options)
其this._modules,即Model對象初始化後的欄位含義為:
root: { // Module對象 state:{ count: 0 } // 傳入Vuex({options})內的state _children: {} // 內部嵌套Module _rawModule: options // 傳入的options對象 }
3.3 installModule
說明:在這裡將對Module進行封裝處理。
處理步驟:
1) 若module.namespaced = true : 此Module將被加入store._modulesNamespaceMap內,其key為Module嵌套的路徑。
if (module.namespaced) { store._modulesNamespaceMap[namespace] = module }
2) 非root Module時:子Module.state註入到父節點的state對象里。
if (!isRoot && !hot) { const parentState = getNestedState(rootState, path.slice(0, -1)) // path.slice(0, -1) 表示只返回前面的父節點 const moduleName = path[path.length - 1] store._withCommit(() => { Vue.set(parentState, moduleName, module.state) }) }
3) 對store進行局部話,這裡主要對module.namespaced= true 的module進行另外處理,其內部的成員都需要進行namespace路徑處理處理。
官方說明:預設情況下,模塊內部的 action、mutation 和 getter 是註冊在全局命名空間的——這樣使得多個模塊能夠對同一 mutation 或 action 作出響應。 如果希望你的模塊具有更高的封裝度和復用性,你可以通過添加 namespaced: true 的方式使其成為帶命名空間的模塊。當模塊被註冊後,它的所有 getter、action 及 mutation 都會自動根據模塊註冊的路徑調整命名。
4) 對module的mutation進行封裝:
①添加到store._mutations數組內,_mutations的key預設為mutation的名稱,如果module.namespaced = true,那麼key就為namespace+mutation的名稱。可多個module註冊同名。
②將module.mutation第二個參數修改為局部化的state。
const entry = store._mutations[type] || (store._mutations[type] = []); entry.push(function wrappedMutationHandler(payload) { handler.call(store, local.state, payload); });
5) 對module的action進行封裝:
①添加到store._actions數組內,_actions的key預設為action的名稱,如果module.namespaced = true,那麼key就為namespace+action的名稱。可多個module註冊同名。
②將module.action第二個參數修改為局部化和root的state。
onst entry = store._actions[type] || (store._actions[type] = []); entry.push(function wrappedActionHandler(payload) { let res = handler.call( store, { dispatch: local.dispatch, commit: local.commit, getters: local.getters, state: local.state, rootGetters: store.getters, rootState: store.state }, payload ); if (!isPromise(res)) { res = Promise.resolve(res); } if (store._devtoolHook) { return res.catch((err) => { store._devtoolHook.emit('vuex:error', err); throw err; }); } else { return res; } });
6) 對module的getter進行封裝:
①添加到store._wrappedGetters數組內,_wrappedGetters的key預設為action的名稱,如果module.namespaced = true,那麼key就為namespace+action的名稱。只能單一註冊。
②module.mutation第一個參數修改為局部化和root的state。
if (store._wrappedGetters[type]) { if (__DEV__) { console.error(`[vuex] duplicate getter key: ${type}`); } return; } store._wrappedGetters[type] = function wrappedGetter(store) { return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ); };
7) 若當前module含有子module時,遍歷當前model的_children屬性,迭代執行installModule。
3.4 resetStoreVM
說明:初始化storevm,並負責將getter註冊為計算屬性,並保存緩存特性。
處理步驟:
1) 遍歷wrappedGetters,封裝到store.getters里。
forEachValue(wrappedGetters, (fn, key) => { // 使用computed來利用其延遲緩存機制 // 直接內聯函數的使用將導致保留oldVm的閉包。 // 使用partial返回只保留閉包環境中的參數的函數。 // use computed to leverage its lazy-caching mechanism // direct inline function use will lead to closure preserving oldVm. // using partial to return function with only arguments preserved in closure environment. computed[key] = partial(fn, store) Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for local getters }) })
2) 初始化store._vm,傳入data和computed。
說明:這裡主要把state與getter(computed)進行綁定,state有更改時,getter(computed)進行自動更新,採用的方式就是本地創建一個Vue對象。
store._vm = new Vue({ data: { $$state: state }, computed })
3.5 註冊插件
4. commit()執行了什麼
當在action內調用了commit方法時,其內部步驟如下:
1) 從store._mutations[]數組內讀取對應的mutation名稱的數組。
2) 從獲取到的_mutations[]數組遍歷執行對應的mutation處理程式。
3) 觸發store._subscribers對應的回調。
5. dispatch()執行了什麼
在通過this.$store.dispatch()調用對應的action時,其其內部步驟如下:
1) 與commit()調用的方法類似,從store._actions[]數組內獲取對應的acticon數組。
2) 執行active對應的處理成語並以Promise返回。
End Web開發之路系列文章 菜單載入中...