持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情
前言
大家好,前面连续几篇文章中我们将Vuex中常用的一些API做了源码解读,掌握了其背后的实现原理。其实除了这些常用的API外,Vuex还为我们提供了一些并不常用的辅助函数,如:mapState、mapGetters、mapMutations、mapActions等。那么在你的日常开发中即使一辈子都不用这些函数,也照样不耽误你能开发出牛逼的项目来,但是如果你偶尔用一下你会发现:诶,这玩意儿也还挺好用的。接下来的几篇分享中我们就一一来分析写这些辅助函数的用法及实现原理。
mapState的用法与场景
Vuex状态管理的出现为组件间的数据共享带来了很大的便利。我们知道如果某些数据需要在组件间进行数据共享,那么就要把这些数据定义在Vuex的state中,然后通过Store实例的state属性进行访问。其中在Vuex官方有这样一句话:
由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态
computed:{
name(){
return this.$store.state.name
}
}
如果只有一两个属性这样定义没啥问题,但是如果一个组件中需要用到很多state的时候
再这样定义就略显麻烦了,于是vuex就为我们提供了一个辅助函数:mapState
。其用法也是非常简单,只需要把要用到的state组成数组传给mapState就可以了,代码示例如下:
computed: mapSatate(["name","age","sex"])
需要注意的是:使用这种方式时,数组中的值必须与state中定义的名称保持一致
。但是这样会引出另外一个问题:我们发现这里直接把mapState的返回值定义给了computed,那么如果在组件内部还需要自定义一些其他计算属性该怎么办?也很简单,因为mapState返回的是一个对象,因此我们可以使用扩展运算符来实现:
computed:{
other(){
return xxxx
},
...mapState(["name","age","sex"])
}
源码解析
在了解了mapState的用法及使用场景后我们再来分析下它的实现原理,看看在它的背后都做了哪些事。
- mapState
// src/helpers.js 168行
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)
}
}
// src/helpers.js 9行
export const mapState = normalizeNamespace((namespace, states)=>{
const res = {}
if(process.env.NODE_ENV !== 'production' && !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(){
let state = this.$store.state
let getters = this.$store.getters
if(namespace){
const moudle = getMoudleByNamespace(this.$store, 'mapState', namespace)
if(!module){
return
}
state = moudle.context.state
getters = module.context.getters
}
return typeof val === 'function' ? val.call(this, state, getters) : state[val]
}
res[key].vuex = true
})
return res
})
function normalizeMap(map){
return Array.isArray(map)? map.map(key=>{key,val:key}): Object.keys(map).map(key=>{key,val:key})
}
首先我们看到在定义mapState时并不是直接定义为一个方法,而是调用了一个normalizeNamespace方法(接收一个函数作为参数),并把该方法的返回值(也是一个方法)作为结果赋值给mapState,其实可以拆解为如下样式:
const fn = (namespace, states) => {... return res}
const mapState = (namespace, map)=>{... return fn(namespace, map)}
所以在我们调用mapState时实际上执行的是normalizeNamespace函数体里面return的那个方法,而在return的方法中又去执行传给normalizeNamespace的那个参数,因此整个流程下来就是:
- 首先组件中调用mapState方法并传递namespace和map两个参数进行执行,然后在该函数内部:
- 先检测传进来的namespace参数是否是一个字符串类型,因为这个参数是可以不传的,如果不是字符串则说明值传递了一个参数,因此直接将namespace的值赋值给map,然后让namespace等于一个空字符串
- 最后再调用fn函数,并将执行结果返回
- 而在fn函数体内首先定义一个空对象res,用于保存定义好的计算属性方法
- 检测第二个参数states是否是一个对象或者数组,因为该方法只接收数组或对象类型的参数
- 这里又多出来个normalizeMap函数,该函数的目的很简单就是把传进来的对象或数组统一转换成[{key,val}]的形式
- 然后遍历数组中的每个对象,并以对象中的key作为res对象的属性名,属性值则是一个新的函数mapedState,而这个函数其实就是我们定义在computed中对应的那个计算属性,当我们在组件中调用state状态值对应的计算属性时,实际上执行的就是这里面的代码。下面我们再来看下这里面又干了哪些事
- 首先在store实例上获取到对应的state和getters,以备不时之需
- 然后如果传递了namespace参数,则根据namespace值找到对应的模块,并将模块中的state和getters重新赋值给变量state和getters,还是以备不时之需(因为state中可能定义的是一个方法,在调用时会需要这个参数)。
- 紧接着这一步才是关键,判断对象中的val是否是一个函数,如果是函数则直接让函数执行,并将之前的state和getters作为参数传递过去
- 如果不是函数,则直接将state对象中的对应的值返回过去
以上便是mapState的整体源码解析。
总结
本次我们分享了vuex我们提供的一个不是很常用的辅助函数mapState的使用方法及实现原理,简单总结如下:
首先将state对象中要用到属性名作为数组或对象传递给mapState函数,在该函数中会定义一个新的函数mappedState,并将其保存在res对象中,当我们在组件中调用state状态对应的属性时就会执行这个mappedState方法,然后将真正的状态值返回
好了本次分享就到这里了,欢迎大佬们点赞评论哦!