2025.09.15

模块设计:reactivity 核心理念与架构


💡 核心概念与目标

reactivity 模块的目标是实现对数据的 深度响应式追踪

核心机制: reactive(target) 接受一个 js 对象 target, 返回一个代理对象(Proxy)。代理对象能够:

  1. 追踪读取(Get): 在属性被读取的时候, 进行 依赖收集
  2. 通知写入(Set): 在属性被修改的时候, 向所有的 effect 发送通知, 重新执行

🛠️ 准备工作

  1. 模块入口定义 定义 reactive.ts 作为模块入口,并准备基本的导出结构。
    // reactive.ts
    export function reactive<T extends object>(target: T): T { /* ... */ }
    
    // index.ts (导出)
    export * from './reactive'
    
  2. 增加工具函数 用于严格判断输入是否为一个普通的 js 对象:
    // shared/src/index.ts
    export function isObject<T extends object = Record<string, unknown>>(value: unknown): value is T {
      return value !== null && typeof value === 'object' && !Array.isArray(value)
    }
    

🏛️ 核心设计模式

  1. 逻辑抽象: createReactiveObject 为了支持 reactiveshallowReactivereadonly 等多种 API,我们将核心的代理创建逻辑抽象到 createReactiveObject 函数中, 通过传入不同的 BaseHandler(即不同的 get/set 拦截逻辑),实现逻辑复用和功能定制。
    export function reactive<T extends object>(target: T): T {
      // 委托给通用的创建函数
      return createReactiveObject(target /* , baseHandlers */)
    }
    
  2. 为什么选择 proxy 而不是 Object.defineProperty
特性Object.definePropertyProxy
拦截范围只能拦截属性的截取, 需要递归遍历对象全方位拦截:get/setdeletePropertyownKeys 等。
数组支持无法拦截数组的索引操作和 length 变化, 需要重写数组方法原生支持: 直接拦截数组操作
集合类型不支持原生支持: 可直接用于 MapSet 等 ES6
新增属性得通过特殊方法(Vue.set)原生支持: 直接拦截对象的新增属性操作

createReactiveObject 实现初探

使用 Proxy 拦截 getset 操作,并连接到依赖收集 (track) 和触发更新 (trigger) 机制:

import { isObject } from '@vue/shared'
import { track, trigger } from './effect' // 假设 track/trigger 已定义

function createReactiveObject<T extends object>(target: T): T {
  // 1. 类型守卫:非对象直接返回原始值
  if (!isObject(target))
    return target

  const proxy = new Proxy(target, {
    get(target, key, receiver) {
      // 2. 拦截读取:执行依赖收集
      track(target, key)
      return Reflect.get(target, key, receiver)
    },
    set(target, key, newValue, receiver) {
      // 3. 拦截写入:判断值是否变化,并触发更新
      const result = Reflect.set(target, key, newValue, receiver)
      trigger(target, key)
      return result
    }
  })

  return proxy
}

🔑 遇到的关键问题:多属性的依赖存储

reactive 中,一个对象有多个属性,我们不能像 ref 那样只用一个 Dep 实例来管理整个对象。

我们面临的挑战是: 属性级别的精确追踪, 如能建立一个 非侵入式的、属性级别的依赖存储结构

  1. 粒度要求: 必须为 target 对象的每个 key 分别维护一个独立的 Dep 实例
  2. 映射关系: 需要存储 target -> key(属性) -> Dep(实例) 这三层映射关系
  3. 非侵入性: 必须在 不修改原始对象的前提下, 实现依赖存储