数据监听

Vue 3 中数据监听的实现

众所周知,Vue 2 采用的是 Object.defineProperty()语法来完成针对对象元素的观察监听;而 Vue 3 则采用了别具一格的 Proxy 代理模式来完成对任何复杂数据类型的代理监听。通过对 Vue 两个版本实现数据监听的 api 特性进行对比,我们可以发现:

  • Object.defineProperty 由于每次只能监听对象一个键的 get、set,导致需要循环监听,浪费性能,而 vue3 的 Proxy 可以一次性监听到所有属性
  • Object.defineProperty 无法监听数组,必须通过增强并替换原型链方法的方式处理数组监听,而 Proxy 则可以直接监听数组变化
  • 由于 Object.defineProperty 只能监听对象,导致 vue2 的 data 属性必须通过一个返回对象的函数方式初始化,而 vue3 则更加多元化,可以监听任意数据

在日常开发中,vue3 提供的响应式 api,最常用的便是 reactive、ref、computed 三种。

响应式 API

  • reactive 依托于 Proxy 语法,分别在访问器特性中的 get 中触发 track,set 中触发 trigger 实现数据获取时的依赖收集和数据变化时的触发更新
  • ref 则依托于 RefImpl 实现类中维护 value 属性的 getter 和 setter,使实例在使用 value 属性时分别触发 track 和 trigger 方法,实现依赖收集和触发更新
  • computed 方法依托于 ComputedImpl 实现类,完成对 getter 函数中的数据进行依赖收集,最后通过构造器中 effect 属性,构建一个包含调度器的副作用函数来实现数据更新

reactive.ts

reactive.ts 为我们提供了 reactive、shallowReactive、readonly、shallowReadOnly、isProxy、toRaw、markRaw 等 vue3 框架导出 api。同时维护了诸如 SKIP、IS_REACTIVE、IS_READONLY、RAW 这些用于标识的私有属性:

export interface Target {
  [ReactiveFlags.SKIP]?: boolean // 是否无效标识,用于跳过监听
  [ReactiveFlags.IS_REACTIVE]?: boolean // 是否已被reactive相关api处理过
  [ReactiveFlags.IS_READONLY]?: boolean // 是否被readonly相关api处理过
  [ReactiveFlags.RAW]?: any // 当前代理对象的源对象,即target
}

以及四个基于 WeakMap 数据类型的代理缓存弱键仓库,用于提升代码运行时的内存性能:

export const reactiveMap = new WeakMap<Target, any>()
export const shallowReactiveMap = new WeakMap<Target, any>()
export const readonlyMap = new WeakMap<Target, any>()
export const shallowReadonlyMap = new WeakMap<Target, any>()

Links