Vue3中的响应式系统简要分析

291 阅读3分钟

Vue2响应式缺点

1. 无法监听数组变化

2. 无法监听对象属性添加/删除

此外,在组件初始化的时候就将所有的数据加入响应式系统,有一定的性能消耗。

Proxy API

ES6中有原生的代理功能,完美地解决了Vue2响应式缺点。

代理可以拦截JS引擎内部目标的底层对象操作,这些底层操作被拦截后会触发响应特定操作的陷阱函数。其具体讲解可以参考《深入浅出ES6》P270。

Vue3中默认设置了部分的陷阱函数:./packages/reactivity/src/baseHandler.ts

reactive:对Array/Object代理

调用createReactiveObject

核心方法就是使用proxy,不过还使用了weakMap来做缓存。

其中有对象类型白名单,在白名单中就不需要使用代理;

此外,对于weakMap类型的数据,其proxy的回调函数有所不同,为collectionHandlers;一般对象为baseHandles,就是上一小节的get/set/has等。

baseHandlers

这里主要分析get/set方法。与Vue2一样,主要是做依赖收集与派发更新。

createGetter

1. 由于Vue3使用了很多特定的KEY作为标记,因此要处理此类KEY

2. 对于一些改变数组元素的方法,不需要响应式处理

3. 对于某些Symbol类型的key,不需要响应式处理

4. 依赖收集,track函数

5. 递归处理,因为proxy只能对一层属性做代理;对于深层属性,需要递归处理,代理每一层属性。此外,这么做的好处也是按需加入响应式系统,不需要一开始就处理所有数据,提升性能。

createSetter

同样地,更改值之后,派发更新,trigger

ref:将 string/numer等基础类型变成响应式

原理很简单,类中添加get/set方法

其他:Vue3中的数组重写原因

按理说,由于Vue2中使用Object.definePropert无法监听数组变化,但是当使用了proxy之后,可以监听数组长度,内容变化,如下:

那么,按理说,应该不用重写数组方法以派发更新。

但是,Vue3源码中出现了注释:

Vue3重写的数组方法有一个特点是可以改变原有数组长度。当使用这些数组的时候,数组长度发生了变化,数组的length属性被依赖收集了,那么在某些情况下可能会导致无限循环。所以需要重写数组方法,防止这种情况。

参考链接:github.com/vuejs/vue-n…

小结

通过Proxy API解决了Object.defineProperty无法监听对象元素增删,数组元素变化。

此外,Proxy API无法深度代理对象,因此需要递归。同时Vue3中对数据响应式处理是按需的,不像Vue2组件初始化的时候就对所有数据进行了响应式处理,提升了性能。

此外,Vue3中也重写了数组方法,目的是防止数组长度属性被依赖收集导致无限循环更新的情况。Vue2中是因为Object.defineProperty无法监听数组的元素变化,才重写数组方法。两者目的不同。

最后,Vue3中的依赖收集与派发更新,分别为track与trigger函数,不在此次分析范围。