vue3 使用 TS 进行了完全的重构,改变的地方还是挺多的,比如:
新增的 Composition API(注意:vue3 也支持 Options API)
模块化的 API 调用(可以有效的进行 TreeShaking)
基于 Fragment 的多个根标签
响应式的实现原理
diff 算法优化
生命周期的变化
新增的一些组件,比如:teleport、suspense 这些
.....
主要两个比较核心的变化:
响应式实现原理的改变
diff 算法优化的变化
在 vue2 中响应式核心还是通过 Object.defineProperty 进行实现的。通过 data 方法返回的对象作为 target。这样无论是 简单数据类型 还是 复杂数据类型 ,都可以直接通过 Object.defineProperty 监听 getter 和 setter 行为。
但是,由于 Object.defineProperty 只能监听指定对象、指定属性的响应性,所以 vue 需要对 data 中返回的复杂数据类型进行循环监听。
那么这样,当我们为响应式数据 动态新增属性(为对象新增一个之前不存在的属性,文档)时,会出现失去响应性的问题。
那么为了解决这个问题,vue2 增加了 Vue.set 的 API ,相当于主动触发了一次 Object.defineProperty。但是,这种方式其实并不方便,需要用户主动触发。
所以,vue3 中改用了 Proxy(也是因为浏览器逐渐升级,不再需要过分兼容旧的浏览器)。利用 Proxy 代理的特性解决了这个问题。
Vue2 中的 diff 大家都喜欢把它叫做 双端 Diff 对比 。大致的思路是通过:新旧两组节点的四个端点(新节点组的开头、新节点组的结尾、旧节点组的开头、旧节点组的结尾) 进行对比,并试图找到可以复用的节点。
而 Vue3 中的 diff 大家都喜欢叫它做 快速 Diff
(注意:快速 diff 并不是官网声明的名字,只是国内都这么叫)。里面涉及到了 最长递增子序列 的概念,整体还是有点复杂的。
总体来说,Vue3 带来的变化很大。通过 Composition API,特别是 3.2 之后新增了 语法糖,让 vue 的使用更加接近了原生 js 实现。
Vue3 的响应式实现主要有两个部分:reactive、ref。
reactive 主要是通过 proxy 进行的响应式实现,核心是监听复杂数据类型的 getter 和 setter 行为。
当监听到 getter 行为的时候那么就收集当前的依赖行为,也就是 effect 。当触发 setter 行为的时候,那么就触发刚才收集的依赖。那么此时,所有获取到当前数据的地方都会更新执行,也就是完成了响应性。
但是 proxy 只能监听复杂数据类型,没有办法监听简单数据类型。所以 vue 专门提供了 ref 方法。ref 方法既可以处理简单数据类型、也可以处理复杂数据类型。它的实现在 3.2 之前和 3.2 之后是不同的。3,2 之前主要通过 Object.defineProperty 进行实现,在 3.2 版本的时候,根据社区贡献改为了 get value 和 set value 标记的方式进行实现。这也是为什么 ref 类型的数据必须要通过 .value 的方式使用的原因(本质上是触发 value 方法)。
当 ref 接收复杂数据类型的时候,会直接通过 toReactive 方法,把复杂数据类型交给 reactive 进行处理。
总结:整个的 vue3 响应性,主要就是由这两大块来进行实现的。proxy 处理复杂数据类型,get value 和 set value 处理简单数据类型。核心都是监听 setter 和 getter ,然后触发 effect 的方式。
computed 和 ref 的实现是有一些类似的,比如:
它们本质上都是一个类(ComputedRefImpl)
都是通过 get value 和 set value 监听 getter 和 setter 行为的
但是因为 computed 的计算属性特性(依赖的响应式数据发生变化时,才会重新计算),所以在源码的实现上有一些区别,这个区别主要体现在两个地方:
调度器:scheduler
执行检查(脏状态):_dirty
1️⃣调度器 scheduler
它是作为 ReactiveEffect 的第二个参数存在的回调函数。当触发依赖的时候,会直接执行这个回调函数。
在这个回调函数中,会根据当前的脏值状态来决定是否需要触发依赖。