材质 × 碰撞:Threejs 物理引擎的双重魔法

在 Vue3 的响应式系统中,refreactive 是实现数据驱动的核心 API,它们的底层实现基于 Proxy依赖收集/触发机制。以下是它们的实现原理剖析:


一、reactive 的实现原理

reactive 用于将普通对象转换为响应式对象,其核心逻辑如下:

1. 基于 Proxy 的代理

  • 当调用 reactive(obj) 时,Vue 会使用 Proxyobj 进行代理,拦截以下操作:
    • get: 访问属性时触发依赖收集。
    • set: 修改属性时触发依赖更新。
    • deleteProperty: 删除属性时触发更新。
    • 其他操作(如 has, ownKeys 等)。

2. 依赖收集(Track)

  • get 拦截器中,通过 track(target, key) 收集当前正在执行的副作用函数(effect),将其存储到全局的 targetMap 中。结构为:
    targetMap: WeakMap<Target, Map<Key, Set<Effect>>>
    
  • 每个对象的每个属性都关联一个 Set<Effect>,用于存储依赖它的副作用函数。

3. 依赖触发(Trigger)

  • set 拦截器中,通过 trigger(target, key) 查找 targetMap 中对应的依赖集合,并重新执行所有关联的副作用函数。

4. 深层响应式

  • reactive 是“深层”的:嵌套对象的属性也会被递归代理。
  • get 拦截器中,如果发现属性值是对象,会递归调用 reactive 进行代理。

二、ref 的实现原理

ref 用于将基本类型(如 numberstring)或对象包装为响应式引用,其核心逻辑如下:

1. 值的包装

  • ref 返回一个 RefImpl 对象,结构如下:
    class RefImpl<T> {
      private _value: T;
      public dep = new Set<Effect>();
      constructor(value: T) {
        this._value = convertToReactive(value); // 如果是对象,调用 reactive
      }
      get value() {
        track(this); // 收集依赖
        return this._value;
      }
      set value(newVal) {
        if (hasChanged(newVal, this._value)) {
          this._value = convertToReactive(newVal);
          trigger(this); // 触发依赖
        }
      }
    }
    
  • 对基本类型值,通过 value 属性的 getter/setter 实现响应式。
  • 对对象类型值,内部会调用 reactive 进行代理。

2. 依赖管理

  • 通过 track(this) 收集依赖到 RefImpl 自身的 dep 集合。
  • 通过 trigger(this) 触发依赖更新。

3. 自动解包

  • 在模板中使用 ref 时,Vue 会自动解包,无需通过 .value 访问(编译阶段处理)。

三、关键区别

特性reactiveref
数据类型只接受对象接受任意类型
访问方式直接访问属性(obj.key通过 .value 访问(ref.value
深层响应式默认深层若值为对象,内部使用 reactive
性能开销较高(Proxy 递归代理)较低(基本类型直接劫持)

四、底层依赖系统

Vue3 的响应式系统基于以下核心模块:

  1. effect:副作用函数,在数据变化时重新执行。
  2. track:在 get 操作中收集当前 effect
  3. trigger:在 set 操作中触发所有关联的 effect

全局依赖存储结构:

const targetMap = new WeakMap(); // Target -> Map<Key, DepsSet>

五、总结

  • reactive 通过 Proxy 代理对象,实现深层次的属性监听。
  • ref 通过包装值和劫持 value 属性,统一了基本类型和对象的响应式处理。
  • 两者均依赖 tracktrigger 实现细粒度的依赖管理,确保高效更新。

使用场景建议

  • 对象/数组优先用 reactive,基本类型用 ref
  • 需要统一处理值类型时(如组合式函数返回值),优先用 ref