2025.09.15
模块设计:reactivity 核心理念与架构
💡 核心概念与目标
reactivity 模块的目标是实现对数据的 深度响应式追踪
核心机制: reactive(target) 接受一个 js 对象 target, 返回一个代理对象(Proxy)。代理对象能够:
- 追踪读取(
Get): 在属性被读取的时候, 进行依赖收集 - 通知写入(
Set): 在属性被修改的时候, 向所有的effect发送通知, 重新执行
🛠️ 准备工作
- 模块入口定义
定义
reactive.ts作为模块入口,并准备基本的导出结构。// reactive.ts export function reactive<T extends object>(target: T): T { /* ... */ } // index.ts (导出) export * from './reactive' - 增加工具函数
用于严格判断输入是否为一个普通的
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) }
🏛️ 核心设计模式
- 逻辑抽象:
createReactiveObject为了支持reactive、shallowReactive、readonly等多种 API,我们将核心的代理创建逻辑抽象到createReactiveObject函数中, 通过传入不同的BaseHandler(即不同的get/set拦截逻辑),实现逻辑复用和功能定制。export function reactive<T extends object>(target: T): T { // 委托给通用的创建函数 return createReactiveObject(target /* , baseHandlers */) } - 为什么选择
proxy而不是Object.defineProperty
| 特性 | Object.defineProperty | Proxy |
|---|---|---|
| 拦截范围 | 只能拦截属性的截取, 需要递归遍历对象 | 全方位拦截:get/set、deleteProperty、ownKeys 等。 |
| 数组支持 | 无法拦截数组的索引操作和 length 变化, 需要重写数组方法 | 原生支持: 直接拦截数组操作 |
| 集合类型 | 不支持 | 原生支持: 可直接用于 Map、Set 等 ES6 |
| 新增属性 | 得通过特殊方法(Vue.set) | 原生支持: 直接拦截对象的新增属性操作 |
createReactiveObject 实现初探
使用 Proxy 拦截 get 和 set 操作,并连接到依赖收集 (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 实例来管理整个对象。
我们面临的挑战是: 属性级别的精确追踪, 如能建立一个 非侵入式的、属性级别的依赖存储结构
- 粒度要求: 必须为
target对象的每个key分别维护一个独立的Dep实例 - 映射关系: 需要存储
target -> key(属性) -> Dep(实例)这三层映射关系 - 非侵入性: 必须在 不修改原始对象的前提下, 实现依赖存储