React + TypeScript 开发中的黄金法则
在 Vue3 的响应式系统中,ref
和 reactive
是实现数据驱动的核心 API,它们的底层实现基于 Proxy 和 依赖收集/触发机制。以下是它们的实现原理剖析:
一、reactive
的实现原理
reactive
用于将普通对象转换为响应式对象,其核心逻辑如下:
1. 基于 Proxy 的代理
- 当调用
reactive(obj)
时,Vue 会使用Proxy
对obj
进行代理,拦截以下操作:get
: 访问属性时触发依赖收集。set
: 修改属性时触发依赖更新。deleteProperty
: 删除属性时触发更新。- 其他操作(如
has
,ownKeys
等)。
2. 依赖收集(Track)
- 在
get
拦截器中,通过track(target, key)
收集当前正在执行的副作用函数(effect
),将其存储到全局的targetMap
中。结构为:targetMap: WeakMap<Target, Map<Key, Set<Effect>>>
- 每个对象的每个属性都关联一个
Set<Effect>
,用于存储依赖它的副作用函数。
3. 依赖触发(Trigger)
- 在
set
拦截器中,通过trigger(target, key)
查找targetMap
中对应的依赖集合,并重新执行所有关联的副作用函数。
4. 深层响应式
reactive
是“深层”的:嵌套对象的属性也会被递归代理。- 在
get
拦截器中,如果发现属性值是对象,会递归调用reactive
进行代理。
二、ref
的实现原理
ref
用于将基本类型(如 number
、string
)或对象包装为响应式引用,其核心逻辑如下:
1. 值的包装
ref
返回一个RefImpl
对象,结构如下:class RefImpl<T> { private _value: T; public dep = new Set<Effect>(); constructor(value: T) { this._value = convertToReactive(value); // 如果是对象,调用 reactive } get value() { track(this); // 收集依赖 return this._value; } set value(newVal) { if (hasChanged(newVal, this._value)) { this._value = convertToReactive(newVal); trigger(this); // 触发依赖 } } }
- 对基本类型值,通过
value
属性的getter/setter
实现响应式。 - 对对象类型值,内部会调用
reactive
进行代理。
2. 依赖管理
- 通过
track(this)
收集依赖到RefImpl
自身的dep
集合。 - 通过
trigger(this)
触发依赖更新。
3. 自动解包
- 在模板中使用
ref
时,Vue 会自动解包,无需通过.value
访问(编译阶段处理)。
三、关键区别
特性 | reactive | ref |
---|---|---|
数据类型 | 只接受对象 | 接受任意类型 |
访问方式 | 直接访问属性(obj.key ) | 通过 .value 访问(ref.value ) |
深层响应式 | 默认深层 | 若值为对象,内部使用 reactive |
性能开销 | 较高(Proxy 递归代理) | 较低(基本类型直接劫持) |
四、底层依赖系统
Vue3 的响应式系统基于以下核心模块:
effect
:副作用函数,在数据变化时重新执行。track
:在get
操作中收集当前effect
。trigger
:在set
操作中触发所有关联的effect
。
全局依赖存储结构:
const targetMap = new WeakMap(); // Target -> Map<Key, DepsSet>
五、总结
reactive
通过 Proxy 代理对象,实现深层次的属性监听。ref
通过包装值和劫持value
属性,统一了基本类型和对象的响应式处理。- 两者均依赖
track
和trigger
实现细粒度的依赖管理,确保高效更新。
使用场景建议:
- 对象/数组优先用
reactive
,基本类型用ref
。 - 需要统一处理值类型时(如组合式函数返回值),优先用
ref
。