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返回。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM