Vue 3 的响应式系统是其核心亮点之一,它让数据变化自动触发视图更新。ref 和 reactive 是 Composition API 中最常用的响应式工具,但它们在用法、适用性和底层机制上存在明显差异。
一、基础概念:ref 和 reactive 是什么?
Vue 的响应式系统基于 Proxy 和 Reflect API 实现,能"监听"数据的读写操作,并在变化时通知依赖(如模板或计算属性)。
1.1 ref:响应式引用,适用于任何类型
ref 是一个函数,它将任意 JavaScript 值(基本类型或对象)包装成一个响应式引用(Ref)。这个引用有一个 .value 属性,用于访问或修改内部值。
为什么需要 ref?
Vue 需要一个"容器"来追踪值的变化。对于基本类型(如数字、字符串),它们不是对象,无法直接用 Proxy 代理,所以 ref 用一个对象"包裹"它们。
简单示例:
javascript
import { ref } from 'vue'
const count = ref(0) // 创建响应式数字
const user = ref({ name: 'Alice' }) // 创建响应式对象
const items = ref([1, 2, 3]) // 创建响应式数组
// 在模板中使用:{{ count }} 或 {{ user.name }}
// 变化时:count.value++ // 视图自动更新
import { ref } from 'vue'
const count = ref(0)
const object = ref({ value: 1 })
const array = ref([1, 2, 3])
reactive 专用于创建响应式对象,仅支持对象类型:
javascript
import { reactive } from 'vue'
const state = reactive({
count: 0,
nested: { value: 1 }
})
const list = reactive([1, 2, 3])
二、核心差异分析
2.1 数据类型支持
ref 支持所有 JavaScript 数据类型:
基本类型:string、number、boolean、null、undefined、symbol
引用类型:Object、Array、Function、Map、Set
reactive 仅支持引用类型:
有效:Object、Array、Map、Set
无效:所有基本类型
2.2 访问方式差异
ref 需要通过 .value 属性访问:
javascript
const count = ref(0)
console.log(count.value) // 读取
count.value = 1 // 写入
reactive 直接访问属性:
javascript
const state = reactive({ count: 0 })
console.log(state.count) // 读取
state.count = 1 // 写入
2.3 实现机制对比
ref 基于对象的属性访问器:
javascript
class RefImpl {
constructor(value) {
this._value = value
}
get value() {
track(this, 'value')
return this._value
}
set value(newVal) {
this._value = newVal
trigger(this, 'value')
}
}
reactive 基于 Proxy 代理:
javascript
function reactive(target) {
return new Proxy(target, {
get(obj, key) {
track(obj, key)
return obj[key]
},
set(obj, key, value) {
obj[key] = value
trigger(obj, key)
return true
}
})
}
三、reactive的缺点
3.1 响应式丢失问题
javascript
// 错误示例:响应式丢失
const state = reactive({ count: 0, name: '' })
const { count, name } = state
// 正确方案:使用 toRefs
const state = reactive({ count: 0, name: '' })
const { count, name } = toRefs(state)
3.2 重新赋值问题
javascript
// 错误:失去响应式
let state = reactive({ count: 0 })
state = reactive({ count: 1 })
// 正确:修改属性
const state = reactive({ count: 0 })
state.count = 1
// 或使用 ref
const state = ref({ count: 0 })
state.value = { count: 1 }
四、适合场景
4.1 ref 适用场景
- 基本类型数据(字符串、数字、布尔值等)
- 需要在响应式系统外管理的引用(如 DOM 元素、定时器)
- 组合式函数的返回值
- 需要重新赋值的响应式变量
- 模板引用(template refs)
4.2 reactive 适用场景
- 相关联的复杂对象状态(如表单数据、配置对象)
- 需要保持引用一致性的嵌套数据结构
- 多个相关属性的集合(如分页参数、用户信息)
- 需要直接访问属性而不需要 .value 的场合
4.3 选择原则
- 优先使用 ref:当处理独立值、基本类型或需要明确类型提示时
- 选择 reactive:当管理具有多个相关属性的复杂对象状态时
- 保持一致性:在同一项目中建立统一的使用规范