前言
若想快速地理解一个方法的源码,学习并掌握此方法,是不可避免的。快速通道
辅助方法
下面列举的辅助方法非常重要,看懂它们就能看懂mapState, mapGetters等辅助函数的源码。不需用一上来就要搞懂这些方法,但要明白它们的功能作用,结合着辅助函数的源码一起看。
normalizeNamespace
返回一个函数,它包含两个参数:命名空间和映射。它将规范化名称空间,然后返回传入的函数,来处理新的名称空间和映射。
function normalizeNamespace (fn) {
return (namespace, map) => {
// 当参数namespace,不是String类型,而是一个对象:'Array' 或 'Object'
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
// namespace的最后一个字符,如果不是 '/',就拼接上
namespace += '/'
}
return fn(namespace, map)
}
}
isValidMap
验证参数 map 类型:'Array' 或 'Object'。
function isValidMap (map) {
return Array.isArray(map) || isObject(map)
}
isObject
验证参数 obj 是否为 'Object'。
export function isObject (obj) {
return obj !== null && typeof obj === 'object'
}
normalizeMap
规范化 map 参数,且 map 必须为:'Array' 或 'Object'。例如:
-
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}]
function normalizeMap (map) {
if (!isValidMap(map)) {
return []
}
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
数组方法:
-
isArray() ,用于确定传递的值是否是一个 Array。
-
map() ,返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。它按照原始数组元素顺序依次处理元素。
注意事项:
- 不会对空数组进行检测。
- 不会改变原始数组。
getModuleByNamespace
依据 namespace(模块的空间名称),在 store 实例中查找指定的模块。如果模块不存在,则打印错误信息。
function getModuleByNamespace (store, helper, namespace) {
// _modulesNamespaceMap 是 store 用于存储模块的空间名称的一个对象
const module = store._modulesNamespaceMap[namespace]
if (__DEV__ && !module) {
console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
}
return module
}
mapState 辅助函数
源码
export const mapState = normalizeNamespace((namespace, states) => {
const res = {}
// __DEV__ 开发环境下,值为:true。
// isValidMap 验证参数类型是否为:'Array' 或 'Object'
if (__DEV__ && !isValidMap(states)) {
console.error('[vuex] mapState: mapper parameter must be either an Array or an Object')
}
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState () {
// 默认获取根模块下的state,getters
let state = this.$store.state
let getters = this.$store.getters
// namespace为真,意味着将模块的空间名称字符串作为了mapState函数的第一个参数。
// 它会让所有绑定都自动将该模块作为上下文。
if (namespace) {
// 获取指定模块
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
// 获取指定模块下的state,getters,以覆盖默认的
state = module.context.state
getters = module.context.getters
}
// val可以是字符串或函数
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
}
// 为devtools标记vuex getter
res[key].vuex = true
})
return res
})
使用方式
以下所举代码示例,来自vuex官网文档。
- 第一个参数(namespace)是对象:'Array' 或 'Object' 类型。
- 参数是 'Object'
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
'Object'对象中的属性值,可以是一个函数。这是mapState源码中,会对 val 进行判断的原因。看下面的源代码。
// val可以是字符串或函数
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
- 参数是 'Array'
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
- 第一个参数(namespace)是字符串:'String' 类型。
namespace为真,意味着将模块的空间名称字符串作为了mapState函数的第一个参数。它会让所有绑定都自动将该模块作为上下文。
还记得上面这句话吗?它是对mapState中部分源码的注释,下面的举例,想必会让你更好地理解这段话。官方文档详细介绍。
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
}
mapGetters 辅助函数
源码
export const mapGetters = normalizeNamespace((namespace, getters) => {
const res = {}
// __DEV__ 开发环境下,值为:true。
// isValidMap 验证参数类型是否为:'Array' 或 'Object'
if (__DEV__ && !isValidMap(getters)) {
console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object')
}
normalizeMap(getters).forEach(({ key, val }) => {
// 将namespace和val拼接并赋值给val,用于获取相应的getter。注意,
// 此时的namespace(命名空间)已被normalizeNamespace函数修改。
val = namespace + val
res[key] = function mappedGetter () {
// namespace存在,且获取不到相应的模块,则阻止运行。
if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
return
}
// 开发环境下,getters中不存在val,则阻止运行并打印错误信息。
if (__DEV__ && !(val in this.$store.getters)) {
console.error(`[vuex] unknown getter: ${val}`)
return
}
// 获取相应的getter
return this.$store.getters[val]
}
// 为devtools标记vuex getter
res[key].vuex = true
})
return res
})
mapGetters源码并不复杂,唯一需要注意的地方可能是这段代码:
// namespace指命名空间,val指相应的getters属性
val = namespace + val
在上面的源码注释中已说明了它的作用:获取相应的getter。但这要分成两种情况,这里再详细说明一下:
- namespace为空字符串(即 '' ),则获取根模块下的getter。
- namespace不为空,则获取指定模块下的getter。
若是对这种获取相应getter的方式,仍旧有疑惑,建议重新阅读vuex源码中关于注册getter部分的源码。也就是,下面这段代码(为简便,registerGetter部分的代码未贴出,大家可自行翻阅)。
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
想必你已注意到namespacedType变量,namespace指命名空间,key指相应的getters属性。这就是,怎么存的,就怎么取。
使用方式
由于mapGetters的使用方式类同于mapState,所以这里就不再举例。若是不太清楚,大家可查阅官网文档。
mapMutations 辅助函数
源码
export const mapMutations = normalizeNamespace((namespace, mutations) => {
const res = {}
// __DEV__ 开发环境下,值为:true。
// isValidMap 验证参数类型是否为:'Array' 或 'Object'
if (__DEV__ && !isValidMap(mutations)) {
console.error('[vuex] mapMutations: mapper parameter must be either an Array or an Object')
}
normalizeMap(mutations).forEach(({ key, val }) => {
// mapMutations 支持载荷,所以res[key],也就是 mappedMutation 函数需要接受参数
res[key] = function mappedMutation (...args) {
// 从store获取commit方法
let commit = this.$store.commit
// 判断 namespace 是否存在
if (namespace) {
// 获取指定的模块
const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
if (!module) {
return
}
//若 namespace(命名空间)存在 则从指定的模块上下文中获取commit方法
commit = module.context.commit
}
// 判断 val 是否为函数,因为传入 mapMutations 的参数是 'Object'时,它的属性值
// 可以是字符串或函数。
return typeof val === 'function'
? val.apply(this, [commit].concat(args))
: commit.apply(this.$store, [val].concat(args))
}
})
return res
})
使用方式
mapMutations使用方式同mapState一样,只不过,它是在methods对象中调用。
methods: {
...mapMutations({
'increment', // 字符串
// 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
add: 'increment' , // 字符串,起别名
inc: function (commit, payload) { console.log(commit, payload) }, // 函数
})
}
mapActions 辅助函数
源码
export const mapActions = normalizeNamespace((namespace, actions) => {
const res = {}
// __DEV__ 开发环境下,值为:true。
// isValidMap 验证参数类型是否为:'Array' 或 'Object'
if (__DEV__ && !isValidMap(actions)) {
console.error('[vuex] mapActions: mapper parameter must be either an Array or an Object')
}
normalizeMap(actions).forEach(({ key, val }) => {
// mapActions 支持载荷,所以res[key],也就是 mappedAction 函数需要接受参数
res[key] = function mappedAction (...args) {
// 从store获取dispatch方法
let dispatch = this.$store.dispatch
if (namespace) {
// 获取指定模块
const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
if (!module) {
return
}
//若 namespace(命名空间)存在 则从指定的模块上下文中获取commit方法
dispatch = module.context.dispatch
}
// 判断 val 是否为函数,因为传入 mapActions 的参数是 'Object'时,它的属性值
// 可以是字符串或函数。
return typeof val === 'function'
? val.apply(this, [dispatch].concat(args))
: dispatch.apply(this.$store, [val].concat(args))
}
})
return res
})
使用方式
mapActions使用方式同mapMutations一样,它也是在methods对象中调用。
methods: {
...mapActions({
'increment' , // 字符串
// 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
add: 'increment' , // 字符串,起别名
inc: function (commit, payload) { console.log(commit, payload) }, // 函数
})
}
createNamespacedHelpers 辅助函数
createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数。其功能作用理解起来可能有点不明所以,但结合其源码和示例看,你就能很容易明白。
源码
export const createNamespacedHelpers = (namespace) => ({
mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace)
})
使用方式
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'increment'
])
}
}
结束语
辅助函数的代码并不复杂,很是容易理解,尤其是对已经阅读过vuex是如何注册state、getter、action和mutation源码的同学。若是,同学们还没读过相关的vuex源码,不妨看看本人写的vuex解读。最后,求点赞。