2025.09.17
进阶掌握:reactivity 的高级特性与陷阱
🎯 设计目标:健壮性与性能优化
在基础的 reactive 实现之上,我们必须解决一系列实际使用中产生的陷阱和性能问题,确保响应式系统具备以下特性:
- 缓存一致性: 避免重复代理导致的依赖分裂
- 性能节流: 避免不必要的副作用函数重新执行
- 深度与互操作性: 支持嵌套对象和
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 逻辑 ...
}