ref 和 reactive 的区别是什么?
在 Vue 3 中,ref 和 reactive 都是用于创建响应式数据的核心 API,但它们在适用场景、访问方式和底层原理上有明显的区别。
以下是详细的对比总结:
1. 核心区别速览表
| 特性 | ref | reactive |
|---|---|---|
| 支持的数据类型 | 所有类型 (基本类型 + 对象/数组) | 仅引用类型 (对象、数组、Map、Set) |
| JS/TS 中访问 | 需要 .value (如 count.value) |
直接访问 (如 state.count) |
| 模板中访问 | 自动解包,不需要 .value |
直接访问 |
| 重新赋值 | 可以直接替换整个对象 | 不能直接替换整个对象 (会丢失响应性) |
| 解构 | 传递 ref 对象本身保持响应性 |
直接解构属性会丢失响应性 (需配合 toRefs) |
| 底层原理 | Object.defineProperty (基本类型) / reactive (对象类型) |
Proxy |
2. 详细对比与代码示例
A. 数据类型支持
- ref: 既可以定义基本数据类型(String, Number, Boolean),也可以定义对象。
- reactive: 只能定义对象或数组。如果传入基本类型,Vue 会报警告且无法产生响应式。
javascript
// ref
const count = ref(0); // OK
const user = ref({ name: 'A' }); // OK
// reactive
const state = reactive({ count: 0 }); // OK
const num = reactive(0); // ❌ 错误用法,不起作用
B. 访问方式 (Script vs Template)
- ref: 在 JS 代码中必须通过
.value读写;在<template>中 Vue 会自动解包,不需要写.value。 - reactive: 无论在哪里都是直接访问属性。
javascript
// Script
const count = ref(0);
console.log(count.value); // 必须加 .value
const state = reactive({ count: 0 });
console.log(state.count); // 直接访问
// Template
// <p>{{ count }}</p> <!-- 不需要 .value -->
// <p>{{ state.count }}</p> <!-- 直接访问 -->
C. 对象的整体替换 (重要区别)
这是开发中最容易踩坑的地方。
- ref: 可以直接替换
.value,响应性依然存在。 - reactive: 不能直接将变量重新赋值为一个新对象,否则会切断与
Proxy的联系,导致失去响应性。
javascript
// ref
const user = ref({ name: 'Alice' });
user.value = { name: 'Bob' }; // ✅ 依然是响应式的
// reactive
let state = reactive({ name: 'Alice' });
state = { name: 'Bob' }; // ❌ 变量 state 被重新赋值了,不再是响应式 Proxy 对象
// 正确做法:
Object.assign(state, { name: 'Bob' }); // ✅ 修改属性,保持响应性
// 或者包裹在 ref 中使用
D. 解构带来的问题
- reactive: 如果对 reactive 对象进行解构,解构出的基本类型变量会失去响应性。
- ref: ref 本身就是个包装对象,通常传递 ref 本身,不会去解构它的
.value。
javascript
const state = reactive({ count: 0, name: 'Vue' });
// ❌ 失去响应性,count 只是一个普通的数字 0
let { count } = state;
count++; // state.count 不会变
// ✅ 解决方案:使用 toRefs
const { count: countRef } = toRefs(state);
countRef.value++; // state.count 也会变
3. 底层原理简述
- reactive: 基于 ES6 的
Proxy实现。它直接代理整个对象,拦截对象的读取和设置操作。 - ref:
- 如果是基本类型:ref 创建一个包含
.value属性的对象(RefImpl),通过get和set拦截对.value的访问。 - 如果是对象类型:ref 内部会自动调用
reactive将其转换为 Proxy,然后放在.value中。
- 如果是基本类型:ref 创建一个包含
4. 最佳实践:该用哪一个?
这在社区中主要有两种观点,你可以根据团队习惯选择:
观点一:一把梭全用
ref(官方文档目前的倾向)- 理由:统一心智模型,不用区分什么时候用 ref 什么时候用 reactive。虽然需要写
.value,但可以通过 IDE 插件(如 Volar)自动补全,且避免了reactive无法整体赋值和解构丢失响应性的问题。
- 理由:统一心智模型,不用区分什么时候用 ref 什么时候用 reactive。虽然需要写
观点二:对象用
reactive,单值用ref- 理由:将相关联的状态聚合在一起(类似 Vue 2 的
data),代码语义更清晰,且不需要到处写.value。
- 理由:将相关联的状态聚合在一起(类似 Vue 2 的
总结建议:
如果你是 Vue 3 新手,或者希望减少心智负担,建议优先使用 ref。当你需要处理逻辑紧密相关的一组状态,且确定不会对对象整体重新赋值时,可以使用 reactive。
右滑查看面试常问