这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战
在上一篇笔记中:Vuex 4源码学习笔记 - 通过dispatch一步步来掌握Vuex整个数据流(五)
我们通过dispatch的调用,一步步的看到Vuex的内部工作流程,这也是看源码的最好的方式,只要捋清楚大概主流程后,再去看那些细枝末节就容易多了。
今天我们就来看看这几个Vuex的辅助函数,分别为mapState、mapGetters、mapActions、mapMutations、createNamespacedHelpers,从名字看到,它们是辅助函数,意思就是,不用它们我们也可以使用Vuex,使用它们只不过是让我们更加方便的应用Vuex。
注意:这几个函数并不能使用在Vue3的Compositon API中,一会从下面的代码我们就可以看到,这几个辅助函数内都用到了this来访问当前的组件实例,而在Setup中,this还没有被创建,所以不能使用这些辅助函数。
mapState
使用方式
在组件中,如果我们需要访问Vuex中的,我们可以通过计算属性的方式
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
但当一个组件需要获取多个状态的时候,就会写很多的计算属性,为了解决这个问题Vuex提供了 mapState
辅助函数帮助我们生成计算属性,可以少写一写代码,比如:
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState
传一个字符串数组。
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
同样的,如果组件已经有其他的计算属性,我们也可以使用对象展开运算符进行添加
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
原理探究
首先mapState的代码在src/helpers.js
文件中
其实这个代码很简单,也就30行左右,下面我们来看里面做了什么
/**
* 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}
*/
export const mapState = normalizeNamespace((namespace, states) => {
const res = {}
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 () {
// 这个函数相当于我们自己写的计算属性函数
// 获取vuex中的state
let state = this.$store.state
// 获取vuex中的getters
let getters = this.$store.getters
if (namespace) {
// 通过namespace获取module模块
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
// 获取module的state
state = module.context.state
// 获取module的getters
getters = module.context.getters
}
// val.call,这里val就是我们的箭头函数state => state.products.all,函数内的this为当前组件,state为传入的vuex的state
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res
})
normalizeNamespace函数主要是用来统一处理成标准格式的namespace和map数据,然后使用处理的标准数据作为参数调用传入的函数。
/**
* 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.
用来统一处理成标准格式的namespace和map数据,然后使用处理的标准数据作为参数调用传入的函数
* @param {Function} fn
* @return {Function}
*/
function normalizeNamespace (fn) {
return (namespace, map) => {
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/'
}
return fn(namespace, map)
}
}
然后通过normalizeMap函数将数组格式数据和对象格式数据统一处理成标准格式的数据,它的注释已经写的很清楚,入参和返回值。
/**
* Normalize the map
* normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
* normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
* @param {Array|Object} map
* @return {Object}
*/
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] }))
}
最后返回的数据格式为一个对象,如下,经过mapState函数封装的函数
{
products: function mappedState () {/* ... */}
}
mapGetters、mapMutations、mapActions
在看完了mapState的代码后,其实mapGetters、mapMutations、mapActions就都是差不多的了,都是会先走normalizeNamespace函数来统一标准化输入,然后通过normalizeMap函数统一处理成标准格式的数据。然后最后返回一个对象格式的key-value格式的数据,可以让我们使用对象展开运算符进行混入。
createNamespacedHelpers
最后一个辅助函数,我们使用 mapState
、mapGetters
、mapActions
和 mapMutations
这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
}
通过使用 createNamespacedHelpers
创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
那么createNamespacedHelpers
函数的原理也十分简单,4行代码,通过每个函数的bind方法,十分的巧妙的绑定了namespace参数。
/**
* Rebinding namespace param for mapXXX function in special scoped, and return them by simple object
* @param {String} namespace
* @return {Object}
*/
export const createNamespacedHelpers = (namespace) => ({
mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace)
})
今天我们通过mapState来看到了它的实现原理,其他几个函数也和它差不多,我们可以通过断点的方式一步步看到它的执行流程。
一起学习更多前端知识,微信搜索【小帅的编程笔记】,每天更新