在Vue3的Composition API中,ref
和reactive
是构建响应式数据的核心工具。许多开发者对它们的选择存在困惑:何时用ref
的.value
?何时用reactive
的直接访问?为何解构会丢失响应性?本文从原理、场景到实战陷阱,为你彻底厘清差异,助你写出更健壮的Vue代码。
一、本质区别:设计目标决定了使用场景
ref
:为独立数据单元而生
ref
可包装任意类型(字符串、数字、对象等),通过.value
属性实现响应式。其核心机制是利用Object.defineProperty
(基本类型)或内部调用reactive
(对象类型)实现数据劫持。
典型场景:基本类型值(如计数器)、需整体替换的对象、DOM元素引用。
reactive
:为复杂状态树优化
仅支持对象或数组,基于Proxy
深度代理整个对象,实现细粒度的响应追踪。直接访问属性即可触发更新,但解构或整体替换会丢失响应性。
典型场景:表单数据、嵌套对象(如user.profile.address
)、无需替换根引用的局部状态。
二、关键差异:从语法到行为的深度解析
访问方式:.value
是核心分水岭
ref
在脚本中必须通过.value
读写值,但模板中自动解包:
const count = ref(0);
count.value++; // ✅ 脚本中修改
模板中直接使用{{ count }}
。
reactive
直接操作属性,无额外语法:
const state = reactive({ count: 0 });
state.count++; // ✅
响应性丢失:解构与赋值的陷阱
reactive
解构属性或整体替换时直接丢失响应性:
let { count } = state; // ❌ 解构后为普通值
state = { count: 1 }; // ❌ 替换失效
修复方案:用toRefs
转换解构,或用Object.assign
合并更新。
ref
解构后仍通过.value
保持响应:
const { value } = countRef; // ✅ value.value++ 有效
性能与原理:底层实现的取舍
ref
对基本类型优化更好(轻量级劫持),对象类型内部转为reactive
,性能一致。reactive
的Proxy
在深层嵌套对象中更高效,但初始化开销较大。
三、实战指南:何时选ref
?何时选reactive
?
优先用ref
的场景
- 基本类型数据(字符串、布尔值等)。
- 需跨组件传递的独立值(
props
传递ref
更安全)。 - 需要整体替换的对象(如从API拉取全新配置)。
优先用reactive
的场景
- 关联属性组(如表单的
{name, email, submitted}
)。 - 深度嵌套对象(如
user.orders[0].products
)。 - 无需替换根引用的对象(如组件局部状态)。
混合使用:平衡简洁性与灵活性
const loading = ref(false); // 独立状态
const form = reactive({
user: ref({ name: "Alice" }), // 嵌套ref自动解包!
items: []
});
form.user.name = "Bob"; // ✅ 直接操作属性
四、避坑总结:那些容易踩的雷
reactive
直接赋值失效:永远用Object.assign
或逐属性修改。- 解构
reactive
属性变普通值:务必用toRefs
转换。 - 异步闭包陷阱:在异步回调中需重新获取
ref
的.value
(如setTimeout
内)。 - 模板中误写
.value
:渲染[object Object]
(因模板自动解包,无需.value
)。
终极建议:
- 小型项目:统一用
ref
,减少心智负担。- 中大型项目:基础类型用
ref
,关联对象用reactive
,混合时用toRefs
解构。
响应式编程的本质是数据与UI的绑定关系。理解ref
和reactive
的底层逻辑,方能写出如臂使指的Vue3代码。