阅读 1541

Vue 3.0 源码逐行解析(一):响应式模块(1)

前言

vue 3.0 源码刚出没多久 本人平常开发惯用vue 之前看过 vue 2.0 的一些源码 这次准备做一次逐行分析 3.0 的源码 欢迎大家在下方留言 大家一起讨论。

vue 3.0 源码特色:

  • 结构清晰
    通过yarn的工作区方式,简单来说就是功能模块按照package.json下的workspace字段,解耦划分成一个个文件夹(自带package.json,不懂的跳转:yarn 文档),每个功能可以自己 yarn dev 编译出相应模块文件,换句话说 vue 3.0 的响应式模块 完全可以抽离出来用作其他项目 这是之前vue 2.0 做不到的
  • Typescript重构
    之前vue 2.0 是用 Flow 来做静态类型代码检查,后面发现这个团队不靠谱 Flow项目烂尾了,根本拼不过微软大大的TS,现在TS的前端市场占比也很高,因此本次也是直接狠下心用Typescript重构了。
    之前本人有个项目用了vue+TS,但总觉得自己的使用很初级,高阶使用 如范型之类的,都没有落地接触过,这个源码中有非常多的TS最佳实践,高阶TS使用。所以看这个源码还能同时很好地学习 TS ,一举两得。
  • Proxy改造
    看过2.0源码的或者稍微了解过vue响应式原理的都知道2.0使用的是Object.defineProperty做数据劫持的。这次也是直接改造成Proxy(ES6语法),性能不像Object.defineProperty需要一上来就遍历所有,性能更加优异,下面会继续讨论这块的。

vue 3.0 源码如何有效地看呢:

  1. 必看
    主目录的package.json,每个项目应该首先看这个文件。因为它可以让我们了解源码支持哪些scripts,比如 test 我们可以yarn test跑一下测试脚本试试。workspace字段代表工作区的内容即功能模块代码位置,就不多说了。
    还有一些前端工程化发布流程相关的加餐知识,学到了也可以应用到自己的项目中,这块可以下节附赠给大家。
  2. 先关注某个功能模块的主流程
    前面说了 workspace 的概念,我们可以从其中一个功能模块开始,看主流程文件,中间遇到函数回调,先看字面意思,看完主流程再去一个个切进去深入看。如:先从 reactivity 文件夹下的 reactive.ts 响应式主流程看起。
  3. 语法文档
    因为有很多 TS / ES6 的语法,建议配合着 TS 文档及 ES6 语法来看,遇到不会的语法赶紧查
  4. yarn dev
    调试源码,编译生成相应文件,自己写一个html引入来测试,这块下节给大家说一下

正文

先看这块,我将源码的注释翻译了一下,感觉还是不够立体,需要品味一番

语法上看,用到了 TS 的范型接口的概念

  • 什么是泛型,这是一种 OOP 后端语言常用的概念,即下图中的<T>,可以从字面的意思推导出: 泛指的,不确定的类型。传进来什么类型后续就用什么类型,比如这里Set如果传入Number类型,add方法的入参也必须是Number类型
  • 为什么说是接口,因为 TS 核心对 ES6 的 Set Map WeakSet WeakMap都做了封装 写入 TS 声明文件(.d.ts),这样 TS 就能对这些类型进行类型推断,静态检查

语义上看,主要是为了构造 targetMap 这个存储依赖关系的WeakMap 这里用WeakMap的原因在于,WeakMap键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内,不影响引用计数,只要其他引用都清除,垃圾回收自动回收掉该对象内存,正好适合该场景:你需要存储proxy化的target对象,一旦其他target对象引用清除,这边targetMap也需要自动清除该对象,有助于防止内存泄漏。
目的就是为了构造出下方这种格式,具体 Dep 的泛型 ReactiveEffect 是什么需要到 effect.ts再看,现在关注这个结构。

{
  target:{
    key: Dep  
  }   
}
复制代码

再看下一块

这边源码注释翻译就是这4个weakmap存储着原始数据到观察者的映射 这里注意这个<->符号代表的是双向的,即原始数据 -> 观察者,观察者 -> 原始数据。
这边为什么要存储双向的,应该还是从通过牺牲空间复杂度来换取更高效的时间复杂度考虑,因为这样存储获取的时间复杂度是O(1),并且还能获得一些其余的好处比如一些自带的性能极佳的方法

这边主要是做了一些预备操作,比如声明集合的类型,正则表达式等,以及是否可观察的函数,这边我已经注释了,对主流程没有太大影响。


接下来属于核心流程

对外暴露的reactive方法是proxy化,这边对只读数据进行了特殊处理readonly函数,这里还需要对值做判断,如果这个值是响应式的proxy直接返回响应式版本。其实最后的核心proxy化步骤都是createReactiveObject这个函数

这边是proxy化的核心流程,其实看着还是比较简单,主要是proxy的handler这一块下一节会深入进去。


最后是一些对外暴露的函数,字面意思应该都看得懂,这里注意获取原始数据这里,最后直接或操作符了observed,有两种可能,一种是非可观察的白名单即上文所提到的,一种是基本数据类型


尾言

这节主要讲了响应式的核心文件及主流程,内容并不复杂,有问题可以下方留言,互相讨论。下节会深入到文件effect.ts,这块也是源码中理解的难点,并且会附送源码中的一些工程化的实践。