H5W3
当前位置:H5W3 > HTML5 > 正文

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

本文地址:H5W3 » Vuex源码阅读new Vuex.Store()内部实现

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址