辅助函数
Vuex为我们提供了几个辅助函数,便于我们在项目中使用store实例的状态数据。函数列表如下:
mapState: 映射store的state属性到组件的computed属性中。mapGetters:映射store的getters属性到组件的computed属性中。mapActions:映射store的actions属性到组件的methods属性中。mapMutations:映射store的mutations属性到组件的methods属性中。createNamespacedHelpers:创建一个带命名空间的辅助函数,它们在使用前都已经绑定在了给定的命名空间。
以下是一个使用mapState的例子:
import { mapState } from 'vuex'
export default {
computed: mapState({
count: state => state.count, // 普通使用,$store.state.count
countAlias: "count", // $store.state.count
})
}
2
3
4
5
6
7
# 一、工具函数
在讲解辅助函数之前,我们先来看看几个辅助函数用到的工具函数。
# 1. normalizeMap
normalizeMap用于将给定的对象转换成一个包含键值对的数组,定义如下:
function normalizeMap(map) {
return Array.isArray(map) // 是数组
? map.map(key => ({ key, val: key })) // 将数组转为[{key: 元素, val: 元素}]
// 将对象转为[{ key: key, val: val }],非Object会返回空数组
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
2
3
4
5
6
如下例子:
normalizeMap({
count: state => state.count,
countAlias: "count",
})
// => [{ key: "count", val: state => state.count }, { key: "countAlias", val: "count" }]
normalizeMap(['count', 'name', 'age'])
// => [{ key: "count", val: "count" }, { key: "name", val: "name" }, { key: "age", val: "age" }]
2
3
4
5
6
7
8
9
# 2. normalizeNamespace
normalizeNamespace接收一个函数,并且返回一个接收namespace和map作为参数的新函数。返回的新函数会对namespace和map进行参数规范化,并将规范化后的参数传递给原始函数执行,返回结果。定义如下:
function normalizeNamespace(fn) {
return (namespace, map) => {
// 参数规范化
if (typeof namespace !== 'string') {
// namespace不是字符串则将namespace赋值给map后置空
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
// namespace以/结尾
namespace += '/'
}
return fn(namespace, map)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 如果
namespace不是字符串,则将namespace赋值给map,namespace置空。 - 如果
namespace是字符串且不以/结尾,则在结尾添加/ - 返回一个接收格式化后的
namespace和map作为参数的新函数。
# 3. getModuleByNamespace
getModuleByNamespace函数根据指定的namespace从stores实例中获取模块,获取到则返回,否则输出错误。定义如下:
function getModuleByNamespace(store, helper, namespace) {
const module = store._modulesNamespaceMap[namespace] // 从模块映射表中获取模块
if (process.env.NODE_ENV !== 'production' && !module) {
// 获取不到输出错误
console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
}
return module
}
2
3
4
5
6
7
8
可见,获取模块时会从store._modulesNamespaceMap中查找获取,通过前面的文章我们可以知道,带有命名空间的模块会被添加到store._modulesNamespaceMap中,store._modulesNamespaceMap是一个以命名空间为key,模块实例为值的对象。
# 二、辅助函数
所有的辅助函数都是调用normalizeNamespace生成的一个函数接收namespace和map两个参数并的新函数。执行时会对其进行参数规范化,然后调用normalizeMap函数将map转换为[{key, val}]数组,最后再遍历处理后返回结果,其返回结果是一个包含键值对的对象,因此可以使用扩展运算符...将结果展开。
# 1. mapState
export const mapState = normalizeNamespace((namespace, states) => {
const res = {}
// 将states转为[{key, val}]后遍历
normalizeMap(states).forEach(({ key, val }) => {
// res[key]是一个函数,因此可以在Vue实例中使用computed的属性访问
res[key] = function mappedState() {
let state = this.$store.state // 从当前Vue实例获取$store.state
let getters = this.$store.getters // 从当前Vue实例获取$store.getters
if (namespace) {// 带了命名空间
// 从store._modulesNamespaceMap中获取命名空间对应的模块
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
// 从模块中的局部上下文中获取state和getters
// context是new Store时,执行installModule时生成的context
state = module.context.state
getters = module.context.getters
}
// val是个function,则以当前Vue实例绑定this后,传入state和getters作为参数执行返回结果
// 否则返回state[val]
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
}
// mark vuex getter for devtools
// 将当前函数标记为来自vuex,提供给devtools
res[key].vuex = true
})
return res
})
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
mapState返回一个新函数,函数接收namespace和states作为参数,由normalizeNamespace的分析可知,namespace格式化可能是空字符串,也可能是带/结尾的字符串,states则是通过normalizeNamespace格式化后的map。返回的新函数会执行如下操作:
- 定义
res对象用于存储返回的结果 - 将
states转为[{key, val}]后遍历 - 令
res[key]为一个新函数mappedState,该函数会执行如下操作:- 令
state为当前Vue实例的$store.state,getters为当前Vue实例的$store.getters - 如果
namespace存在,则从store._modulesNamespaceMap中获取命名空间对应的模块,如果找不到模块则返回undefined,否则令state和getters为模块中的局部上下文context的state和getters - 如果
val是个函数,则以当前Vue实例绑定this后,传入state和getters作为参数执行返回结果,否则返回state[val] - 为
res[key]标记vuex属性,提供给devtools使用
- 令
- 返回最终的
res对象
所有的辅助函数基本都遵循该处理思路,后文不再赘述,仅对差异做说明,详细见源码注释。
# 2. mapGetters
export const mapGetters = normalizeNamespace((namespace, getters) => {
const res = {}
normalizeMap(getters).forEach(({ key, val }) => {
val = namespace + val
res[key] = function mappedGetter() {
// 不存在的模块
if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
return
}
// 不存在的getter
if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {
console.error(`[vuex] unknown getter: ${val}`)
return
}
// 从$store.getters中获取
return this.$store.getters[val]
}
// mark vuex getter for devtools
// 给devtools的vuex标记
res[key].vuex = true
})
return res
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mapGetters会根据命名空间直接从store.getters中获取,不会从module.context.getters中获取。
# 3. mapMutations
export const mapMutations = normalizeNamespace((namespace, mutations) => {
const res = {}
normalizeMap(mutations).forEach(({ key, val }) => {
res[key] = function mappedMutation(...args) {
let commit = this.$store.commit
if (namespace) {
// 从store._modulesNamespaceMap中获取命名空间对应的模块
const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
if (!module) {
return
}
// 从模块中的局部上下文中获取commit方法
commit = module.context.commit
}
return typeof val === 'function' // 第二个参数是函数形式:function(commit, ...args) { commit('xxx', ...args) })
? val.apply(this, [commit].concat(args)) // 映射为vm.val(commit, ...args)
: commit.apply(this.$store, [val].concat(args)) // 映射为this.$store.commit(val, ...args)
}
})
return res
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
当val是函数时,commit方法会被作为第一个参数传入,且函数的this会被绑定为当前Vue实例。
import { mapMutations } from 'vuex'
export default {
data() {
return {
type: "increment"
}
}
methods: {
...mapMutations(['increment', 'incrementBy']),
...mapMutations({
inc: function(commit, payload) {
commit(this.type, payload)
}
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4. mapActions
export const mapActions = normalizeNamespace((namespace, actions) => {
const res = {}
normalizeMap(actions).forEach(({ key, val }) => {
res[key] = function mappedAction(...args) {
let dispatch = this.$store.dispatch
if (namespace) {
// 获取模块
const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
if (!module) { // 不存在的模块
return
}
// 局部上下文中的dispatch
dispatch = module.context.dispatch
}
return typeof val === 'function'// 第二个参数是函数形式:function(dispatch, ...args) { dispatch('xxx', ...args) }
? val.apply(this, [dispatch].concat(args)) // 映射为vm.val(dispatch, ...args)
: dispatch.apply(this.$store, [val].concat(args)) // 映射为this.$store.dispatch(val, ...args)
}
})
return res
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mapActions和mapMutations的处理方式基本类似。
# 5. createNamespacedHelpers
当我们需要从某个命名空间中获取state、getters、mutation、action时,这时候我们会在上面提供的辅助中,传入namespace参数。但是有时候,我们可能会同时想创建基于同一个命名空间的mutation、action、state、getters,如果每次都传入namespace会较为麻烦,所以vuex提供了createNamespacedHelpers方法,该方法会返回一个对象,该对象包含mapState、mapGetters、mapMutations、mapActions四个基于给定命名空间namespace的辅助绑定函数。其实现就是使用bind方法,预设namespace参数:
export const createNamespacedHelpers = (namespace) => ({
mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace)
})
2
3
4
5
6
以上就是Vuex提供的所有辅助函数的实现,所有辅助函数的本质就是生成一个可以访问store的对象供Vue实例的计算属性或者methods使用。