5.2 mapMutations
export const mapMutations = normalizeNamespace((namespace, mutations) => { const res = {} if (__DEV__ && !isValidMap(mutations)) { console.error('[vuex] mapMutations: mapper parameter must be either an Array or an Object') } normalizeMap(mutations).forEach(({ key, val }) => { res[key] = function mappedMutation (...args) { // Get the commit method from store let commit = this.$store.commit if (namespace) { const module = getModuleByNamespace(this.$store, 'mapMutations', namespace) if (!module) { return } commit = module.context.commit } return typeof val === 'function' ? val.apply(this, [commit].concat(args)) : commit.apply(this.$store, [val].concat(args)) } }) return res })
mapMutations
与 mapState
的实现大体相似,主要的不同就在下面这段代码:
return typeof val === 'function' ? val.apply(this, [commit].concat(args)) : commit.apply(this.$store, [val].concat(args))
这里也是像 mapState
一样处理了函数的调用类型和普通的调用类型,例如:
mapMutations({ foo: (commit, num) => { commit('foo', num) }, bar: 'bar' })
当是函数的调用类型时,则将 commit
作为第一个参数,并把额外的参数一并传入,所以才有的 val.apply(this, [commit].concat(args))
这段代码 ;
当是普通的调用类型时,则直接执行 commit
,其中 val
对应的就是该命名空间下需要调用的 mutations
方法名,然后再接收额外的参数,即
commit.apply(this.$store, [val].concat(args))
5.3 mapGetters
export const mapGetters = normalizeNamespace((namespace, getters) => { const res = {} if (__DEV__ && !isValidMap(getters)) { console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object') } normalizeMap(getters).forEach(({ key, val }) => { // The namespace has been mutated by normalizeNamespace val = namespace + val res[key] = function mappedGetter () { if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) { return } if (__DEV__ && !(val in this.$store.getters)) { console.error(`[vuex] unknown getter: ${val}`) return } return this.$store.getters[val] } // mark vuex getter for devtools res[key].vuex = true }) return res })
这个也没什么好说的了,拿到命名空间 namespace
,直接拼接上 val
通过 this.$store.getters[val]
进行访问。简单举个例子:
第一种情况
// 第一种 mapGetters(['first/foo'])
这种情况下 namespace
被处理成了空字符串,map
被处理成了 ['first/foo']
,遍历 map
,此时 val = 'first/foo'
,那么 val = namespace + val
处理后 val
仍然等于 first/foo
,所以最后就相当于调用 this.$store.getters['first/foo']
再来看第二种情况
// 第二种 mapGetters('first', ['foo'])
这种情况下 namespace
被处理成了 first/
,map
被处理成了 ['foo']
,遍历 map
,此时 val = 'foo'
,那么 val = namespace + val
处理后 val
就等于 first/foo
,所以最后仍然是相当于调用 this.$store.getters['first/foo']
5.4 mapActions
export const mapActions = normalizeNamespace((namespace, actions) => { const res = {} if (__DEV__ && !isValidMap(actions)) { console.error('[vuex] mapActions: mapper parameter must be either an Array or an Object') } normalizeMap(actions).forEach(({ key, val }) => { res[key] = function mappedAction (...args) { // get dispatch function from store let dispatch = this.$store.dispatch if (namespace) { const module = getModuleByNamespace(this.$store, 'mapActions', namespace) if (!module) { return } dispatch = module.context.dispatch } return typeof val === 'function' ? val.apply(this, [dispatch].concat(args)) : dispatch.apply(this.$store, [val].concat(args)) } }) return res })
简单看了一下,处理流程跟 mapMutations
几乎一模一样,就不多说了
5.5 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) })
该方法是根据传入的命名空间 namespace
创建一组辅助函数。巧妙之处就是先通过 bind
函数把第一个参数先传入
import { createNamespacedHelpers } from 'vuex' const { mapState, mapActions } = createNamespacedHelpers('first/second') export default { computed: { ...mapState({ a: 'a', // 相当于 first/second/a b: 'b', // 相当于 first/second/b }) }, methods: { ...mapActions([ 'foo', // 相当于 first/second/foo 'bar', // 相当于 first/second/bar ]) } }
💡 心得体会
首先,我一直有一个阅读源码的想法,但却因为能力有限迟迟没有行动,之后在一次与大佬的交流中,我发现了自己的不足,没有深入学习,即只停留在「会用」的阶段,却没有做到知其然知其所以然。说实话,这样真的很难受,每次用某个库时,出现了某个问题只会先看考虑是否自己调用的方式有问题,然后上搜索引擎找答案,长期这样自己也很难有进步。
所以,因为以下三点原因,我准备靠自己好好看一下 Vuex
源码:
Vuex
的核心源码比较少,对于像我一样第一次阅读源码的人比较友好
- 深入学习了常用的库以后,在使用的时候遇到问题,可以快速地找到问题根源
- 不能只停留在成熟的库的表面,要学习它们的思想、技术,这样有助于自己的成长
刚开始不知道自己能花多久时间看完 Vuex
的核心源码,我初步给自己定了 15
天的期限,预计每天至少看 2
小时。于是我把 Vuex
的源码 fork
并 clone
了下来,第一天简单地找了一下核心代码的位置,然后非常粗略地看了一下源码里的大致流程。同时,我去 Vuex
官方文档里重新仔仔细细地回顾了一下所有的核心使用方法
接下来的时间我就按照我本文的阅读顺序进行源码的阅读
这里总结几点阅读源码的「心得体会」吧:
- 对于这个库的使用一定要十分熟练,即明白各种方法的使用,强烈建议把官方文档吃透(「重点」)
- 找到核心代码的位置,从入口文件开始,一步步看
- 多看源码中的英文注释,看不懂的可以用翻译,这些注释基本上能帮你理解这段代码的作用
- 遇到看不懂的地方可以先打个备注,因为它可能与后面的某些代码有所联系,等之后回头来看之前看不懂的代码时,就会明白了
- 阅读源码的过程中,看到某些变量或函数时,先看命名,因为这些命名的字面意思基本上就代表了它的作用,然后要学会联想到这个正常的调用是什么样的,这样更便于理解
- 多多利用编译器的搜索功能。因为有时你看到的函数或变量可能在别的地方也有用到,为了方便寻找,可以利用好编译器的搜索功能(包括当前「本地搜索」和「全局搜索」)
本地搜索
全局搜索
🌱 问答环节
这里放上几个群友对于这次阅读源码问我的问题:
「Q1:」 你是怎么看源码的?有看别人的视频或者别人的文章吗?
「A1:」 没有看别人的视频或者文章,就当时自己思考了一下该如何看源码,列了一个步骤,就这样摸索着看完了,觉得还挺有意思的
「Q2:」 光自己看能看懂吗?
「A2:」 说实话确实有些地方挺难看懂的,但结合着源码自带的英文注释,基本上能把大致的思路理清,然后看不懂的地方就先做上记号并暂时略过,等到看了更多的代码了以后,回过头来就发现似乎看懂了些。最后要说的就是,源码真不是一遍就能看懂的,真的是要反反复复多看几遍,才能理解其中的原理
「Q3:」 看完源码后,你能自己手写出来吗?
「A3:」 emmmm...这可能有点难度,但是我觉得手写一些核心代码,实现一个简陋的 Vuex
还是可以做到的吧,而且我觉得很有必要自己再去手写一下核心代码,因为这又是一次对源码的巩固,并且我也已经开始在写一个简陋版的 Vuex
了,放在仓库的 myVuex
文件夹下
📌 最后
若本文对于 Vuex
源码阅读有任何错误的地方,欢迎大家给我提意见,一定虚心听取你们的指正,
Vuex
源码阅读仓库可以点击文末的 「阅读原文」 查看,若觉得不错的,也可以点个🌟 「star」 🌟 支持一下我。
这篇文章我真的很用心了,你们忍心不给点个赞 👍