响应式 | 简述 reactive API 的实现原理和关键特性

95 阅读4分钟

响应式对象与只读对象

Vue3 响应式系统的关键 API 与 rective相关的有以下四个:

  • reactive(): 创建深度响应式对象

  • readonly(): 创建只读响应式对象

  • shallowReactive(): 创建浅层响应式对象

  • shallowReadonly(): 创建浅层只读响应式对象

reactive 方法的实现

export function reactive<T extends object>(target: T): Reactive<T> {
  // 如果目标对象已经是只读的,直接返回
  if (isReadonly(target)) {
    return target
  }
  // 创建响应式对象
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers, 
    reactiveMap
  )
}

主要实现原理

使用WeakMap存储原始对象到代理对象的映射:

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

通过Proxy创建响应式对象:

const proxy = new Proxy(
  target,
  targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)

核心方法createReactiveObject

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>,
) {
  // 1. 检查目标是否为对象
  if (!isObject(target)) {
    if (__DEV__) {
      warn(`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(target)}`)
    }
    return target
  }

  // 2. 检查目标是否已经是Proxy
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }

  // 3. 检查缓存中是否已存在对应的Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

  // 4. 获取目标类型并验证
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }

  // 5. 创建新的Proxy
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  
  // 6. 缓存Proxy
  proxyMap.set(target, proxy)
  return proxy
}

主要功能和步骤:

  • 参数说明:

  • target: 要代理的目标对象

  • isReadonly: 是否是只读

  • baseHandlers: 基本类型的处理器

  • collectionHandlers: 集合类型的处理器

  • proxyMap: 缓存代理对象的WeakMap

  • 主要操作流程:

    • 类型检查:

      • 检查目标是否为对象,非对象直接返回

      • 开发环境下会发出警告

    • 避免重复代理:

      • 检查目标是否已经是代理对象

      • 检查缓存中是否已有对应的代理

    • 目标类型判断:

      enum TargetType {

      INVALID = 0,
      
      COMMON = 1,    // Object 和 Array
      
      COLLECTION = 2 // Map、Set、WeakMap、WeakSet
      

      }

    • 创建代理:

      • 根据目标类型选择不同的handlers

      • 使用Proxy API创建代理对象

      • 将新创建的代理存入缓存

    • 性能优化:

      • 使用WeakMap作为缓存,避免内存泄漏

      • 对同一个对象多次调用reactive会返回同一个代理实例

    • 特殊处理:

      • 对于Collection类型(Map/Set等)使用特殊的handlers

      • 对于已经是代理的对象有特殊的处理逻辑

总结:createReactiveObject是Vue响应式系统的核心方法,它通过一系列的检查和优化,确保能够安全且高效地为对象创建响应式代理。它的实现考虑了性能优化、类型安全、边界情况等多个方面。

关键特性

  • 深度响应式转换

  • 自动解包嵌套的ref

  • 对集合类型(Map/Set等)的特殊处理

  • 缓存代理对象避免重复创建

  • 只读代理的支持

关于 WeakMap

WeakMap 的特性:

// WeakMap示例
const weakMap = new WeakMap();
let obj = { data: "some data" };

// 设置键值对
weakMap.set(obj, "metadata");

// 如果obj被设为null
obj = null;
// obj作为键和对应的值都会被自动回收

与普通 Map 的区别:

// 普通Map
const normalMap = new Map();
let obj = { data: "some data" };

// 设置键值对
normalMap.set(obj, "metadata");

// 即使obj被设为null
obj = null;
// Map中的引用仍然存在,不会被垃圾回收

主要优势:

  • 弱引用特性:
// WeakMap中的键是弱引用
const wm = new WeakMap();
{
    const obj = { id: 1 };
    wm.set(obj, "data");
} // obj离开作用域后会被回收
  • 自动垃圾回收:
let obj = { id: 1 };
const wm = new WeakMap();
wm.set(obj, "data");

obj = null; // 对象可以被垃圾回收

内存泄漏的防范:

// 不会造成内存泄漏的情况
const wm = new WeakMap();
function process(obj) {
    wm.set(obj, "processed");
    // 函数结束后,如果obj没有其他引用
    // WeakMap中的数据会被回收
}

使用场景:

// 适合用于关联数据存储
class DOMTracker {
    private elementData = new WeakMap();
    
    track(element: HTMLElement, data: any) {
        this.elementData.set(element, data);
        // 当element被删除时,数据会自动清理
    }
}

总结WeakMap避免内存泄漏的原因:

1. 弱引用机制:

  • WeakMap的键是弱引用
  • 不会阻止垃圾回收器工作
  • 当对象只被WeakMap引用时可以被回收
  1. 自动清理:
  • 不需要手动清理数据
  • 避免了忘记清理导致的内存泄漏
  • 更安全的内存管理
  1. 适用场景:
  • 临时数据关联
  • 缓存实现
  • DOM元素关联数据
  1. Vue中的应用:
  • 存储原始对象到代理对象的映射
  • 当组件销毁时自动清理
  • 避免长期运行导致的内存累积