Vuex源码解析

Vuex将所有的数据统一存放到内部store中,然后内部实例化一个vm监听store的变化,业务代码中使用到store时就会产生依赖。 业务代码中使用的各种mutationaction最终都是尝试修改内部的store状态,store真正变化时就会自动由Vue通知到业务组件。

入口

和其他Vue插件一样,Vuex也会提供一个install方法作为入口,位于store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function install(_Vue) {
// _Vue用于防止重复install
if (Vue && _Vue === Vue) {
// ...
return;
}
Vue = _Vue;
applyMixin(Vue);
}

// applyMixin
export default function (Vue) {
// ...
Vue.mixin({ beforeCreate: vuexInit })
// ...
}

function vuexInit () {
const options = this.$options
// store injection
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
}
}
}

可以看到入口代码很简单,核心之处位于vuexInit方法,它是把我们在Vue实例化选项中的store对象放到了Vue.prototype.$store中,这样我们就可以在任何组件中使用this.$store了。而store选项是我们通过Vuex.Store构造函数生成的,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const store = new Vuex.Store({
state: {
count: 0,
},
mutations: {
increment(state) {
state.count++;
},
},
});

new Vue({
// ...
store,
});

Vuex几乎所有的核心逻辑都是Store了,相关的代码位于store.js中。这个文件初看起来很大,不过其实逻辑大多不难,耐心看下去应该没什么问题,而其中关于vuex module的篇幅又占了很多,随后我们会慢慢感知到。

Store

照常我们先从构造函数入口开始看起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
constructor(options = {}) {
// 一些校验逻辑,略去...

const { strict = false } = options;

// store internal state
this._committing = false; // 用于标识是否处于mutation处理中
this._actions = Object.create(null); // 存放所有定义的actions
this._actionSubscribers = []; // 用于存放subscribeAction api参数的数组
this._mutations = Object.create(null); // 存放所有定义的mutations
this._wrappedGetters = Object.create(null); // 封装的getters,通过闭包替换真正getter的参数
this._modules = new ModuleCollection(options); // module tree,modules.root表示树根
this._modulesNamespaceMap = Object.create(null); // 层级的命名空间路径与module映射
this._subscribers = []; // 用于存放subscribe api参数的数组,类似_actionSubscribers
this._watcherVM = new Vue(); // 主要用于公共api watch方法,监听参数的变化

// bind commit and dispatch to self
const store = this;
const { dispatch, commit } = this;

// 闭包绑定dispatch函数的store参数
this.dispatch = function boundDispatch(type, payload) {
return dispatch.call(store, type, payload);
};
// 闭包绑定commit函数的store参数
this.commit = function boundCommit(type, payload, options) {
return commit.call(store, type, payload, options);
};

// strict mode
this.strict = strict;

const state = this._modules.root.state; // 根module的state

// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root);

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state);

// apply plugins, 基本不会管,省略
}

里面有 3 个函数需要重点看下:

  • ModuleCollection: 由于Vuex可以利用module来模块化,这个类就是用于帮助我们构建一棵 module tree,每当有一个新 module 都会根据其对应的 path 来决定插入到 tree 的何处。整个 module tree 不会考虑 module 是否是 namespaced 的,root 对应的是 path 为空数组的那个

  • installModule: 与ModuleCollection无关,主要用于处理层级 module,有些 module 会带有namespaced属性,此方法会专门进行处理

  • resetStoreVM: 核心逻辑是将_wrappedGetters挂载到一个内部store._vmcomputed上,然后定义store.getters公共api供业务代码使用,而store.getters其实就是取的store._vm.computed,这样就很巧妙的利用了Vue computed的懒惰计算了,每个getter只有在内部的依赖发生变化时才会重新计算,进而业务代码的相关属性也会享受到懒惰计算的好处。

ModuleCollection

先看看入口构造函数:

1
2
3
constructor (rawRootModule) {
this.register([], rawRootModule, false)
}

只有在处理根 module 时会直接调用new ModuleCollection(options),所以只有 root module 的 path 才是空数组,再看看register

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
register (path, rawModule, runtime = true) {
// 一些校验逻辑略去, 检验rawModule中的每个选项类型是否正确,例如检查getters选项中的每一个getter是否都是函数。 其中action可以为函数也可以为对象...

const newModule = new Module(rawModule, runtime) // 构造一个新的module,其中会设置自己的state
if (path.length === 0) {
this.root = newModule // path为空数组时设置树根
} else {
const parent = this.get(path.slice(0, -1)) // 获取父module。 get函数按照path在module tree中查找对应module
parent.addChild(path[path.length - 1], newModule) // 将newModule插入到父module的children中
}

// 若还有子module,则循环递归处理
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
// 注意path的变化,这样所有子module的父module就都是上面的newModule了
this.register(path.concat(key), rawChildModule, runtime)
})
}
}

瞄一眼Module的构造函数:

1
2
3
4
5
6
7
8
9
10
11
constructor (rawModule, runtime) {
this.runtime = runtime
// Store some children item
this._children = Object.create(null)
// Store the origin module object which passed by programmer
this._rawModule = rawModule
const rawState = rawModule.state

// Store the origin module's state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}

一些内部的帮助函数都很简单这里就不一一说了,整个register的逻辑就是构造一个module tree,每个 module 都会设置自己的state,然后会在installModule中用到这棵树。

installModule

这个函数非常核心,看看怎么实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function installModule(store, rootState, path, module, hot) {
const isRoot = !path.length;
// 根据path获取拼接的namespace字符串,会考虑沿途的namespaced module
const namespace = store._modules.getNamespace(path);

// 注册namespace和module的映射关系
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module;
}

// set state
if (!isRoot && !hot) {
// 获取path对应的局部state,所有的state也是根据path组成了一棵树
const parentState = getNestedState(rootState, path.slice(0, -1));
const moduleName = path[path.length - 1];

// _withCommit会在执行fn时令_committing为true,然后就会受到enableStrictMode中watch的保护,这样改变state时就不会触发报错
store._withCommit(() => {
// 就是在这里,相当于所有的state也是根据path组成了一棵树
Vue.set(parentState, moduleName, module.state);
});
}

// 以当前module为上下文,定义了dispatch、commit、getters、state.下面会详细说明
const local = (module.context = makeLocalContext(store, namespace, path));

// 将module.mutaions添加到store._mutations数组中,会考虑namespace。wrappedMutationHandler中的第2个参数state是local的
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key;
registerMutation(store, namespacedType, mutation, local);
});

// 将module.actions添加store._actions数组中,会考虑namespace,store._actions中的每个函数会统一返回Promise。wrappedActionHandler中每个action前4个参数是local的
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key;
const handler = action.handler || action;
registerAction(store, type, handler, local);
});

// 将module.getters添加到store._wrappedGetters数组中,会考虑namespace。_wrappedGetters中每个getter前2个参数是local的
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key;
registerGetter(store, namespacedType, getter, local);
});

// 递归处理子module,注意path的变化
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot);
});
}

这个函数做的事情有点多,归纳一下有以下几点:

  • 设置_modulesNamespaceMap,这个对象的格式为{[namespace: string]: Module }

  • module对应的state插入到state tree中,state treepath为路径,不会考虑namespaced的影响

  • localContext挂载到module.context

  • module.mutaions添加到store._mutations数组中,会考虑namespacewrappedMutationHandler中的第 2 个参数statelocal

  • module.actions添加store._actions数组中,会考虑namespacestore._actions中的每个函数会统一返回PromisewrappedActionHandler中每个action前 4 个参数是local

  • module.getters添加到store._wrappedGetters数组中,会考虑namespace_wrappedGetters中每个getter前 2 个参数是local

  • 递归处理子module

然后有些比较重点的子函数再挨个单独说一下。

getNamespace:

1
2
3
function getNestedState(state, path) {
return path.length ? path.reduce((state, key) => state[key], state) : state;
}

要意识到pathnamespace不是一一对应的,每一段path只有在其namespaced属性为true时才对应一段namespace. 例如path = ['a','b','c','d'],其中c对应的modulenamespaced的,那么最终的namespace字符串为 'c/',而不是'a/b/c/d'.

makeLocalContext

如上所述,会生成局部dispatch, commit, getters and state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* make localized dispatch, commit, getters and state
* if there is no namespace, just use root ones
*/
function makeLocalContext(store, namespace, path) {
const noNamespace = namespace === ''; // 表示与根module的namespace一致

const local = {
dispatch: noNamespace
? store.dispatch // 使用全局的dispatch
: (_type, _payload, _options) => {
// 处理对象风格的提交方式,生成归一化的type、payload、options,见https://vuex.vuejs.org/zh/guide/mutations.html
const args = unifyObjectStyle(_type, _payload, _options);
const { payload, options } = args;
let { type } = args;

if (!options || !options.root) {
type = namespace + type; // 注意这里会处理namespace,生成加长版的type!!!!
// 校验逻辑...
}

return store.dispatch(type, payload);
},

commit: noNamespace
? store.commit
: (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options);
const { payload, options } = args;
let { type } = args;

if (!options || !options.root) {
type = namespace + type; // 注意这里会处理namespace,生成加长版的type!!!!
// 校验逻辑...
}

store.commit(type, payload, options);
},
};

// getters and state object must be gotten lazily
// because they will be changed by vm update
Object.defineProperties(local, {
getters: {
// makeLocalGetters在下面会说,主要是生成短链形式的getters子集
get: noNamespace ? () => store.getters : () => makeLocalGetters(store, namespace),
},
state: {
get: () => getNestedState(store.state, path),
},
});

return local;
}

注意何时一个 module 对应的 namespace 为空?并不是只有根 module 的namespace是空串,只要子modulenamespaced属性是空或 false,那么它就不会在namespace上贡献自己的力量,而是使用parent modulenamespace

全局的dispatchcommit其实定义在构造函数里,他们均硬绑定了store参数

1
2
3
4
5
6
7
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);
};
  • local上的dispatch、commit在业务代码的调用参数上均和全局的dispatch一致,只不过真正执行逻辑时会在type上添加上namespace前缀,这就是他们的区别。

  • local上getter也会考虑namespace,最后生成的getters是快捷键形式的,在业务代码调用时不用写上namespace前缀,内部逻辑会帮忙添加。这块是借助makeLocalGetters做到的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 从所有的getters找到那些key以namespace为前缀的,并生成短链形式的getters子集
function makeLocalGetters(store, namespace) {
const gettersProxy = {};

const splitPos = namespace.length;
// store.getters存放所有的getter,对应的键已经融合了namespace
Object.keys(store.getters).forEach(type => {
// 是否以namespace为前缀
if (type.slice(0, splitPos) !== namespace) return;

// extract local getter type,提取“短链”形式key
const localType = type.slice(splitPos);

// Add a port to the getters proxy.
// Define as getter property because
// we do not want to evaluate the getters in this time.
Object.defineProperty(gettersProxy, localType, {
get: () => store.getters[type],
enumerable: true,
});
});
return gettersProxy;
}
  • local上的state也是局部的,只不过state是基于path的,而不会管namespace的影响。

registerMutation

将 module.mutaions 添加到 store._mutations 数组中

1
2
3
4
5
6
7
function registerMutation(store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = []);
// wrappedMutationHandler作用就是通过闭包为真正的mutation绑定参数
entry.push(function wrappedMutationHandler(payload) {
handler.call(store, local.state, payload);
});
}

唯一需要注意的地方是第二个参数是local.state,而不是store.state

registerAction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function registerAction(store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = []);
entry.push(function wrappedActionHandler(payload, cb) {
let res = handler.call(
store,
{
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state,
},
payload,
cb,
);

// 令最终的action始终返回Promise
if (!isPromise(res)) {
res = Promise.resolve(res);
}

// 插件的影响,略去...
return res;
});
}

mutation类似,真正执行的action前 4 个参数均是local的,另外经过wrappedActionHandler包装的action始终会返回Promise

registerGetter

1
2
3
4
5
6
7
8
9
10
11
function registerGetter(store, type, rawGetter, local) {
// 校验
store._wrappedGetters[type] = function wrappedGetter(store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters, // root getters
);
};
}

_wrappedGetters最大的作用会在resetStoreVM中体现出来。

resetStoreVM

上面提到核心逻辑是将_wrappedGetters挂载到一个内部store._vmcomputed上,然后定义store.getters公共api供业务代码使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function resetStoreVM(store, state, hot) {
const oldVm = store._vm;

// bind store public getters
store.getters = {};
const wrappedGetters = store._wrappedGetters;
const computed = {};

// 将所有的wrappedGetters放到Vue.computed上,这样获取
// getter时实际上是去拿computed,可以懒惰计算
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store);
// store.getters底层利用了_vm的computed,这样只有在依赖改变时才重新计算
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true, // for local getters
});
});

// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
const silent = Vue.config.silent;
Vue.config.silent = true; // 置为true可以令实例化Vue时不报警
// 所有的state和getter都挂载到了Vue实例上
store._vm = new Vue({
data: {
$$state: state,
},
computed,
});
Vue.config.silent = silent;

// enable strict mode for new vm
if (store.strict) {
// 设置只允许在mutation中改变state
enableStrictMode(store);
}

if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.

store._withCommit(() => {
oldVm._data.$$state = null;
});
}
// 销毁旧的store._vm
Vue.nextTick(() => oldVm.$destroy());
}
}

粗略总结下这个函数做的事情:

  • store._wrappedGetters挂载到store._vm.computed上,以此实现响应式getter,调用computed[key]() 会返回store._wrappedGetters[key](store),也就是rawGetter(local.state,local.getters, store.state, store.getters);

  • state挂载到store._vm.data.$$state

  • 设置调用store.getters[key]会返回store._vm[key],即computed上的值

上面的enableStrictMode比较有意思,因为Vuex中规定业务方只能通过mutation来改变store,就是通过这个函数来做到的,核心是利用一个内部变量,只有在这个变量为 true 时,改变 state 触发的 watch 才不会警告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 观察state是否在_committing之外被改动
function enableStrictMode(store) {
store._vm.$watch(
function() {
return this._data.$$state;
},
() => {
if (process.env.NODE_ENV !== 'production') {
// 若_committing不为true,报错
assert(store._committing, `do not mutate vuex store state outside mutation handlers.`);
}
},
{ deep: true, sync: true },
);
}

至此,所有 vuex 内部的核心逻辑就全部看完了。接下来看一些Vuex的公共 api,了解其内部实现。

公共 api

commit、dispatch

最重要最常用的就是这俩了~~

commit

大体是找到所有符合条件的的mutations回调然后执行,内部利用_withCommit可以受到enableStrictMode的豁免。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 commit(_type, _payload, _options) {
// check object-style commit
const { type, payload, options } = unifyObjectStyle(_type, _payload, _options);

const mutation = { type, payload };
/**
* 在registerMutation中设置了_mutations:
*
* const entry = store._mutations[type];
entry.push(function wrappedMutationHandler(payload) {
handler.call(store, local.state, payload);
});
*/
const entry = this._mutations[type];

// 空校验略去...

this._withCommit(() => {
entry.forEach(function commitIterator(handler) {
/**
* handler指向上面的
* function wrappedMutationHandler(payload) {
handler.call(store, local.state, payload);
}
*/
handler(payload); // 调用真正业务方定义的mutaion
});
});

// 调用store.subscribe(fn)时会往_subscribers数组中添加值
this._subscribers.forEach(sub => sub(mutation, this.state));

// 校验逻辑略去...
}

dispatch

与 commit 类似,大体是找到所有符合条件的的action回调然后执行。只不过因为action可以执行异步逻辑,这里会使用Promsie.all保证所有的action全部执行完成再返回结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dispatch(_type, _payload) {
// check object-style dispatch
const { type, payload } = unifyObjectStyle(_type, _payload);

const action = { type, payload };
// 在registerAction中会往store._actions中添加函数
/**
* store._actions: {
* [type:string]: ((payload,cb) => Promise)[]
* }
*/
const entry = this._actions[type]; // 所有符合条件的的`action`

// 校验逻辑略去。。。

this._actionSubscribers.forEach(sub => sub(action, this.state));

return entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload);
}

registerModule

用于动态注册 module,会执行和Store构造函数类似的逻辑

1
2
3
4
5
6
7
8
9
10
11
registerModule(path, rawModule, options = {}) {
if (typeof path === 'string') path = [path];

// 将rawModule加入到整个module tree当中,设置parent和children module
this._modules.register(path, rawModule);

installModule(this, this.state, path, this._modules.get(path), options.preserveState);

// reset store to update getters...
resetStoreVM(this, this.state);
}

unregisterModule

用于动态卸载 module。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unregisterModule(path) {
if (typeof path === 'string') path = [path];

// 校验逻辑略去。。

this._modules.unregister(path); // 从module tree中移除此节点

this._withCommit(() => {
// 从state tree中移除此局部state
const parentState = getNestedState(this.state, path.slice(0, -1));
Vue.delete(parentState, path[path.length - 1]);
});
resetStore(this); // 重置整个store
}

resetStore

重置整个 store,会重新执行 installModule 和 resetStoreVM.

1
2
3
4
5
6
7
8
9
10
11
function resetStore(store, hot) {
store._actions = Object.create(null);
store._mutations = Object.create(null);
store._wrappedGetters = Object.create(null);
store._modulesNamespaceMap = Object.create(null);
const state = store.state;
// init all modules
installModule(store, state, [], store._modules.root, true);
// reset vm
resetStoreVM(store, state, hot);
}

mapXXX 系列

可以猜测到,这些帮助函数主要会利用到闭包来帮助我们处理跟namespace相关的琐碎细节。一起挨个看下。其中mapStatemapMutationsmapActions逻辑类似,这里只说其中一个。

mapState

经过它的处理,会返回一个对象,将mapState的参数转换为了 {[key:string]: ()=> any}形式的对象,可以直接利用它来当做组件的computed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* Reduce the code which written in Vue.js for getting the state.
*
* @param {String} [namespace] - Module's namespace
* @param {Object|Array} states # Object's item can be a function which accept state and getters for param, you can do something for state and getters in it.
* @param {Object}
* @return 返回一个对象,将mapState的参数转换为了 {[key:string]: ()=> any}形式的对象,可以直接利用它来当做组件的computed
*/
// normalizeNamespace:归一化处理参数函数的namespace
export const mapState = normalizeNamespace((namespace, states) => {
const res = {};

// normalizeMap: 用于归一化states参数,因为可以使用多种形式
// normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }]
// normalizeMap({a: 1, b: 2}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }]
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState() {
let state = this.$store.state;
let getters = this.$store.getters;
// 若有namespace,则需要找到其对应module.context的局部api
if (namespace) {
// 根据获取namespace来store._modulesNamespaceMap的映射
const module = getModuleByNamespace(this.$store, 'mapState', namespace);
if (!module) {
return;
}
state = module.context.state; // 局部state
getters = module.context.getters; // 局部getter
}
// val可能是函数或字符串
return typeof val === 'function' ? val.call(this, state, getters) : state[val];
};
});
return res;
});

其中的normalizeNamespace函数,经过它的处理后,参数函数会拿到归一化之后的 namespace,''或者'a/b/c/'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
**
* Return a function expect two param contains namespace and map. it will normalize the namespace and then the param's function will handle the new namespace and the map.
* @param {Function} fn
* @return {Function}
*/
function normalizeNamespace(fn) {
// 这个闭包才是mapState真正本体!!!
return (namespace, map) => {
if (typeof namespace !== 'string') {
map = namespace;
namespace = '';
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/';
}
return fn(namespace, map);
};
}

mapGetters

这个函数的大体逻辑和上面是类似的,只不过在细节上有可以说的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Reduce the code which written in Vue.js for getting the getters
* @param {String} [namespace] - Module's namespace
* @param {Object|Array} getters
* @return {Object} 返回一个对象,将mapGetters的参数转换为了 {[key:string]: ()=> any}形式的对象,可以直接利用它来当做组件的computed.
*/
export const mapGetters = normalizeNamespace((namespace, getters) => {
const res = {};
normalizeMap(getters).forEach(({ key, val }) => {
// thie namespace has been mutate by normalizeNamespace,
// mapGetters中的val始终是字符串
val = namespace + val;
res[key] = function mappedGetter() {
// 校验逻辑略去。。。

// 注意,这里没有使用module的局部getters,而mapState和mapMutations都使用了局部context
return this.$store.getters[val];
};
// mark vuex getter for devtools
res[key].vuex = true;
});
return res;
});

上面为什么是this.$store.getters和而不是和mapState类似的module.context.getters呢?

其实也可以那么写,不过要就要去掉上方的val = namespace + val了,因为module.context.getters是“短链”形式,不记得的看上面的makeLocalGetters

this.$store.getters是在什么时候赋值的呢?主要分为 3 步:

  1. installModule
1
2
3
4
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key; // “长链”形式key
registerGetter(store, namespacedType, getter, local);
});
  1. registerGetter
1
2
3
4
5
6
7
8
store._wrappedGetters[type] = function wrappedGetter(store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters, // root getters
);
};
  1. resetStoreVM中设置store.getters
1
2
3
4
5
6
7
8
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = () => fn(store);
// 这里给store.getter赋值
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true, // for local getters
});
});

经过这 3 步,store.getters就存放了所有”长链”形式 getter。 那么mapGetters中的this.$store.getters[val]就很容易理解了。

createNamespacedHelpers

很简单就是利用闭包绑定mapXXXnamespace:

1
2
3
4
5
6
export const createNamespacedHelpers = namespace => ({
mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace),
});