在 Vue 3 中,ref 和 reactive 是实现响应式数据的两大核心 API,它们在数据类型支持、访问方式、响应性保持及使用场景上存在显著差异,具体对比如下:
一、数据类型支持
1.2、ref
通用性:可包装任意类型的值,包括基础类型(如 string、number、boolean)和复杂类型(如对象、数组、Map、Set)。
内部机制:若传入复杂类型,ref 内部会调用 reactive 将其转换为深度响应式对象。
示例:
const count = ref(0); // 基础类型
const user = ref({ name: 'Alice' }); // 对象类型
1.2、reactive
局限性:仅支持复杂类型(对象、数组、Map、Set),若传入基础类型(如 string、number)会直接返回原值,无响应式。
深度响应:自动递归处理嵌套对象,确保所有层级属性均为响应式。
示例:
const state = reactive({ count: 0 }); // 合法
const num = reactive(10); // 无效,返回原值 10
二、数据访问与修改
2.1、ref
访问/修改:需通过 .value 属性操作数据,但在模板中自动解包,无需写 .value。
示例:
const count = ref(0);
console.log(count.value); // 0(JS 中访问)
count.value++; // 修改值
<template>
<div>{{ count }}</div> <!-- 模板中直接使用,无需 .value -->
</template>
2.2、reactive
直接访问:直接通过对象属性操作数据,无需 .value。
示例:
const state = reactive({ count: 0 });
console.log(state.count); // 0
state.count++; // 直接修改
<template>
<div>{{ state.count }}</div> <!-- 直接访问属性 -->
</template>
三、响应性保持
3.1、ref
解构问题:直接解构会丢失响应性,需配合 toRefs 或 toRef 保持响应性。
示例:
javascript
const user = ref({ name: 'Alice' });
const { name } = user.value; // 解构后失去响应性
// 正确做法:
const { name: refName } = toRefs(user.value); // 保持响应性
3.2、reactive
解构友好性:直接解构对象属性会丢失响应性,但可通过 toRefs 转换。
示例:
const state = reactive({ count: 0 });
const { count } = state; // 解构后失去响应性
// 正确做法:
const { count: refCount } = toRefs(state); // 保持响应性
四、使用场景推荐
4.1、优先使用 ref 的场景
需要处理基础类型(如计数器、布尔标志)。
需要明确数据来源(如通过 .value 区分响应式与非响应式数据)。
需要将数据传递给函数或组合式 API 时,避免引用丢失。
4.2、优先使用 reactive 的场景
管理复杂对象状态(如表单数据、全局状态)。
需要深度响应式且避免频繁使用 .value 的代码结构。
与 Vue 2 风格迁移时,保持对象操作的直观性。
五、性能与实现原理
5.1、ref
底层实现:基于 Object.defineProperty 或 Proxy(复杂类型时调用 reactive)。
性能开销:基础类型需额外包装对象,复杂类型依赖 reactive 的深度递归。
5.2、reactive
底层实现:基于 Proxy,直接拦截对象操作(如属性读写、删除)。
性能优势:对复杂对象操作更高效,但基础类型无法使用。
六、表格对比
特性 | ref |
reactive |
---|---|---|
支持数据类型 | 基础类型 + 复杂类型 | 仅复杂类型 |
访问方式 | .value (JS 中),模板自动解包 |
直接通过属性访问 |
响应性保持 | 解构需 toRefs |
解构需 toRefs |
适用场景 | 基础类型、明确数据来源 | 复杂对象、深度响应式 |
性能 | 基础类型有包装开销 | 对复杂对象更高效 |