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: '' });
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