在 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
代理对象。