Vuex源码阅读(一) new Vuex.Store()内部探究    

Vuex源码阅读(一) new Vuex.Store()内部探究_VuexVuex源码阅读(一) ,介绍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: ''
});

 

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