2025.09.17

进阶掌握:reactivity 的高级特性与陷阱

🎯 设计目标:健壮性与性能优化

在基础的 reactive 实现之上,我们必须解决一系列实际使用中产生的陷阱和性能问题,确保响应式系统具备以下特性:

  1. 缓存一致性: 避免重复代理导致的依赖分裂
  2. 性能节流: 避免不必要的副作用函数重新执行
  3. 深度与互操作性: 支持嵌套对象和 ref 自动解包

陷阱 1: 原始对象重复传入

❌ 问题:依赖分裂

当同一个原始对象(target)多次传入 reactive 时,每次都会创建一个新的代理对象。

const obj = { count: 0 }
const state1 = reactive(obj)
const state2 = reactive(obj)

console.log('state1 === state2 ==> ', state1 === state2) // 结果为 false

相同的原始数据却对应着不同的代理对象,在依赖收集和触发更新时,无法正确同步,导致响应式失效。

✅ 解决方案:引入 reactiveMap 缓存机制 使用一个全局的 WeakMap (reactiveMap) 来存储 target -> proxy 的映射,实现 代理对象的单例化

const reactiveMap: WeakMap<object, any> = new WeakMap<object, any>()

function createReactiveObject<T extends object>(target: T): T {
  // 1. 检查缓存:如果 target 已有代理对象,直接返回
  const existingProxy = reactiveMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

  // ... (创建 proxy) ...

  // 2. 缓存新创建的代理对象
  reactiveMap.set(target, proxy)
  return proxy
}

陷阱 2: 再次传入 reactive

❌ 问题:已经是响应式代理(Proxy

const state1 = reactive({ count: 0 })
const state2 = reactive(state1) // state1 已经是代理对象

✅ 解决方案:WeakSet

  • Vue 官方策略: 通过在代理对象上设置特殊的内部标记(如 __v_isReactive)并在 get 拦截器中识别。
  • 实现策略: 使用一个 WeakSet (reactiveSet) 存储所有已创建的代理对象,在创建前进行检查。
const reactiveSet: WeakSet<object> = new WeakSet<object>()

function createReactiveObject<T extends object>(target: T): T {
  // ... (检查缓存和对象类型) ...

  // 检查传入的 target 是否已经是响应式对象
  if (reactiveSet.has(target)) {
    return target // 避免无限代理链
  }

  // ... (创建 proxy) ...

  reactiveSet.add(proxy) // 保存新创建的代理对象
  return proxy
}

优化 3: 赋相同的值

❌ 问题:不必要的更新触发

const state = reactive({
  count: 0
})

effect(() => {
  console.log('state.count ==> ', state.count)
})

setTimeout(() => {
  state.count = 0
})

✅ 解决方案:set 拦截器中的新旧值比较

// shared/index.ts
export function hasChanged(value: any, oldValue: any): boolean {
  return !Object.is(value, oldValue)
}

// Proxy handler - set
set(target, key, newValue, receiver)
{
  const oldValue = target[key]
  const res = Reflect.set(target, key, newValue, receiver)

  if (hasChanged(newValue, oldValue)) { // 🔑 性能节流:值改变才 trigger
    trigger(target, key)
  }
  return res
}

特性 4: 深度代理

✅ 解决方案:get 拦截器中的递归代理 在 get 拦截器中,对获取到的属性值 res 进行检查:如果是对象,则递归调用 reactive(res) 将其转换为代理对象。

// Proxy handler - get
get(target, key, receiver) {
  track(target, key);
  const res = Reflect.get(target, key, receiver);

  if (isObject(res)) {
    // 🚀 递归代理:实现深度响应式
    return reactive(res);
  }
  return res;
}

特性 5: 自动互操作性

5.1 ref 传入对象: 自动深度响应 解决方案: 在 RefImpl 构造函数和 set value 中,检查传入值是否为对象,如果是,则自动调用 reactive() 转换。

// ref.ts 节选
class RefImpl<T = unknown> {
  constructor(value: T) {
    this._value = isObject(value) ? reactive(value) : value // 构造时自动 reactive
  }

  // ... (set value 逻辑类似)
}

5.2 reactive 包含 ref: 自动解包与同步 📖 读取 (Get) - 自动解包

// Proxy handler - get 节选
// ...
if (isRef(res)) {
  return res.value // 自动返回 ref 的 value
}
return res

✍️ 写入 (Set) - 修改同步 如果旧值是 ref 且新值不是 ref,我们应该修改 ref 内部的值,而不是替换掉整个 ref 实例。

// Proxy handler - set 节选
set(target, key, newValue, receiver) {
  const oldValue = target[key];

  // 🔑 如果旧值是 ref,且新值不是 ref,则直接修改 ref.value
  if (isRef(oldValue) && !isRef(newValue)) {
    oldValue.value = newValue; // 通过 ref.value 的 set 触发更新
    return true;
  }

  // ... 正常 set 逻辑 ...
}