辅助函数
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
使用。