从未看过源码,到底该如何入手?分享一次完整的源码阅读过程(五)

简介: 接上文。

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
})


mapMutationsmapState 的实现大体相似,主要的不同就在下面这段代码:


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 源码:


  1. Vuex 的核心源码比较少,对于像我一样第一次阅读源码的人比较友好


  1. 深入学习了常用的库以后,在使用的时候遇到问题,可以快速地找到问题根源


  1. 不能只停留在成熟的库的表面,要学习它们的思想、技术,这样有助于自己的成长


刚开始不知道自己能花多久时间看完 Vuex 的核心源码,我初步给自己定了 15 天的期限,预计每天至少看 2 小时。于是我把 Vuex 的源码 forkclone 了下来,第一天简单地找了一下核心代码的位置,然后非常粗略地看了一下源码里的大致流程。同时,我去 Vuex 官方文档里重新仔仔细细地回顾了一下所有的核心使用方法


接下来的时间我就按照我本文的阅读顺序进行源码的阅读


这里总结几点阅读源码的「心得体会」吧:


  1. 对于这个库的使用一定要十分熟练,即明白各种方法的使用,强烈建议把官方文档吃透(「重点」


  1. 找到核心代码的位置,从入口文件开始,一步步看


  1. 多看源码中的英文注释,看不懂的可以用翻译,这些注释基本上能帮你理解这段代码的作用


  1. 遇到看不懂的地方可以先打个备注,因为它可能与后面的某些代码有所联系,等之后回头来看之前看不懂的代码时,就会明白了


  1. 阅读源码的过程中,看到某些变量或函数时,先看命名,因为这些命名的字面意思基本上就代表了它的作用,然后要学会联想到这个正常的调用是什么样的,这样更便于理解


  1. 多多利用编译器的搜索功能。因为有时你看到的函数或变量可能在别的地方也有用到,为了方便寻找,可以利用好编译器的搜索功能(包括当前「本地搜索」「全局搜索」


cf3fb715be7485a828037b77255756a8.png

本地搜索

d41805f302e9e37a8e9082015b35e182.png 

全局搜索


🌱 问答环节


这里放上几个群友对于这次阅读源码问我的问题:


「Q1:」 你是怎么看源码的?有看别人的视频或者别人的文章吗?


「A1:」 没有看别人的视频或者文章,就当时自己思考了一下该如何看源码,列了一个步骤,就这样摸索着看完了,觉得还挺有意思的


「Q2:」 光自己看能看懂吗?


「A2:」 说实话确实有些地方挺难看懂的,但结合着源码自带的英文注释,基本上能把大致的思路理清,然后看不懂的地方就先做上记号并暂时略过,等到看了更多的代码了以后,回过头来就发现似乎看懂了些。最后要说的就是,源码真不是一遍就能看懂的,真的是要反反复复多看几遍,才能理解其中的原理


「Q3:」 看完源码后,你能自己手写出来吗?


「A3:」 emmmm...这可能有点难度,但是我觉得手写一些核心代码,实现一个简陋的 Vuex 还是可以做到的吧,而且我觉得很有必要自己再去手写一下核心代码,因为这又是一次对源码的巩固,并且我也已经开始在写一个简陋版的 Vuex 了,放在仓库的 myVuex 文件夹下


📌 最后


若本文对于 Vuex 源码阅读有任何错误的地方,欢迎大家给我提意见,一定虚心听取你们的指正,


Vuex 源码阅读仓库可以点击文末的 「阅读原文」 查看,若觉得不错的,也可以点个🌟 「star」 🌟 支持一下我。


这篇文章我真的很用心了,你们忍心不给点个赞 👍

相关文章
|
2月前
|
测试技术
面试题8: 如何确定测试需求的关键场景和细节?
面试题8: 如何确定测试需求的关键场景和细节?
|
7月前
|
设计模式 缓存 Java
一步步带你读懂 Okhttp 源码
一步步带你读懂 Okhttp 源码
|
8月前
|
消息中间件 网络协议 Java
eventMesh源码学习
eventMesh源码学习
134 0
|
11月前
|
JSON 前端开发 数据可视化
umi3源码探究简析
作为蚂蚁金服整个生态圈最为核心的部分,umi可谓是王冠上的红宝石,因而个人认为对于整个umi架构内核的学习及设计哲学的理解,可能比如何使用要来的更为重要;作为一个使用者,希望能从各位大佬的源码中汲取一些养分以及获得一些灵感
188 0
|
安全 Java
ReentranLock源码学习
线程的三大特性:原子性、可见性、有序性。也就是说满足这个三个特性的操作都是可以保证安全的,如Atomic包、volatile、通过happensBefore原则可以进行线程的安全的判断,这个依据通常是为了避免jvm指令重排。比如通常我们知道的配置信息,如果有多个线程去进行配置信息的修改,则需要进行上锁。或者多个线程修改一个变量时,此时就需要进行上锁了,或者读写分离时,可以考虑ReentrantReadWriteLock等。其本质是解决并行中的问题,将并行转成串行问题进行解决。那怎么上锁才有用呢?锁的状态大部分情况下是互斥的。当然也有特例:ReentrantReadWriteLock的读读是不会
78 0
ReentranLock源码学习
|
Java 数据库连接 mybatis
【SSM框架解析】——前篇:详解动态代理【案例驱动】(案例源码自取)
今天开始要学习SSM框架了,新的开始我决定用博客来记录自己的学习过程,和CSDN上的大佬们一同进步。
【SSM框架解析】——前篇:详解动态代理【案例驱动】(案例源码自取)
|
存储 人工智能 安全
C++学习必备——文章中含有源码
C++学习必备——文章中含有源码
99 0
C++学习必备——文章中含有源码
|
XML 设计模式 缓存
面试必问系列之最强源码分析,带你一步步弄清楚Spring如何解决循环依赖
面试必问系列之最强源码分析,带你一步步弄清楚Spring如何解决循环依赖
29591 6
面试必问系列之最强源码分析,带你一步步弄清楚Spring如何解决循环依赖
|
XML 缓存 JSON
看SpringCloudEureka源码前懂得这些知识事半功倍
看SpringCloudEureka源码前懂得这些知识事半功倍
看SpringCloudEureka源码前懂得这些知识事半功倍