在 Vue 3 中,ref 和 reactive 是两种不同的响应式数据创建方式,它们的区别和实现方式如下:
一、核心区别
| 特性 | ref | reactive |
|---|---|---|
| 适用类型 | 基本类型 + 对象类型 | 仅对象类型(对象/数组/Map/Set) |
| 访问方式 | 通过 .value 访问 |
直接访问属性 |
| 解构响应性 | 需要 toRefs 保持解构后的响应性 |
直接解构会丢失响应性 |
| 深层响应性 | 自动递归代理嵌套对象 | 自动递归代理嵌套对象 |
| 模板自动解包 | 支持(模板中无需 .value) |
直接访问属性 |
二、实现方式
1. ref 的实现
-
核心机制 :通过一个对象包装值(
RefImpl类),利用getter/setter+Proxy实现响应式。 -
源码关键点:
js
class RefImpl<T> {
private _value: T
private _rawValue: T // 原始值(避免对象被 reactive 二次代理)
constructor(value: T) {
this._rawValue = value
this._value = isObject(value) ? reactive(value) : value
}
get value() {
track(this, TrackOpTypes.GET, 'value') // 依赖收集
return this._value
}
set value(newVal) {
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = isObject(newVal) ? reactive(newVal) : newVal
trigger(this, TriggerOpTypes.SET, 'value') // 触发更新
}
}
}
-
特点:
- 对基本类型直接通过
getter/setter监听。 - 对对象类型内部使用
reactive代理。
- 对基本类型直接通过
2. reactive 的实现
-
核心机制 :基于
Proxy代理对象,拦截get/set/deleteProperty等操作。 -
源码关键点:
js
function reactive(target) {
const proxy = new Proxy(target, {
get(target, key, receiver) {
track(target, TrackOpTypes.GET, key) // 依赖收集
const res = Reflect.get(target, key, receiver)
return isObject(res) ? reactive(res) : res // 递归代理嵌套对象
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key) // 触发更新
}
return result
}
})
return proxy
}
-
特点:
- 通过
Proxy实现深层响应式。 - 递归代理嵌套对象(性能优化可通过
shallowReactive避免)。
- 通过
三、使用场景
1. 使用 ref 的场景
- 需要响应式的基本类型值(如
string/number/boolean)。 - 需要显式控制值的引用(如需要重新赋值整个对象时)。
- 需要兼容模板自动解包(模板中无需
.value)。
2. 使用 reactive 的场景
- 需要响应式的复杂对象或数组。
- 需要保持对象引用不变,仅修改属性。
- 需要深层响应式(如嵌套对象自动代理)。
四、最佳实践
-
优先选择
ref对于基本类型或需要频繁重新赋值的对象,使用
ref更直观。 -
复杂对象用
reactive当需要处理嵌套结构且不需要重新赋值整个对象时,
reactive更简洁。 -
解构时用
toRefs解构
reactive对象时,使用toRefs保持响应性:
js
const state = reactive({ count: 0 })
const { count } = toRefs(state) // 解构后仍为 Ref 类型
五、底层原理对比
| 机制 | ref | reactive |
|---|---|---|
| 依赖收集 | 通过 getter 中的 track |
通过 Proxy.get 中的 track |
| 触发更新 | 通过 setter 中的 trigger |
通过 Proxy.set 中的 trigger |
| 性能开销 | 基本类型更低,对象类型与 reactive 相同 |
对象类型高效,嵌套对象递归代理 |
总结
ref:更通用的响应式工具,通过.value访问,适合基本类型或需要重新赋值的对象。reactive:专为对象设计的响应式工具,适合深层嵌套结构,直接访问属性更简洁。- 实现差异 :
ref通过RefImpl类包装值,reactive通过Proxy代理对象。