在 Vue 3 的 Composition API 中,refreactive 是构建响应式数据的两大核心基石。许多开发者在使用时常常感到困惑:到底该用哪个? 何时需要 .value?,为什么解构后会丢失响应性?

本文将从原理、语法、使用场景到实战陷阱,为你彻底厘清两者的差异,助你写出更清晰、更高效的 Vue 代码。


一、核心区别速览表

为了让你一目了然,我整理了这份对比表:

特性 ref reactive
适用类型 所有类型 (基本类型、对象、数组) 仅引用类型 (对象、数组)
访问方式 JS 中需 .value;模板中自动解包 直接通过属性名访问 (无需 .value)
底层原理 ref 内部其实是一个对象 { value: ... },Vue 会把这个对象用 reactive 包装起来 基于 ES6 Proxy 直接代理整个对象
解构风险 直接解构会丢失响应性 直接解构会丢失响应性
替换操作 可以整体替换值 (修改 .value) 不能直接替换整个对象 (会丢失响应性)

二、详细解析与使用场景

2.1. ref:通用型响应式包装器

ref 就像一个“万能容器”,无论是数字、字符串还是对象,它都能装。

  • 基本类型首选:这是 ref 最主要的用途。因为 JavaScript 的基本类型(Number, String, Boolean)无法被 Proxy 代理,所以必须用 ref 包装成一个对象,通过 .value 属性来实现响应式。
import { ref } from 'vue'
const count = ref(0); // 数字
const name = ref('Vue'); // 字符串

// 在 JS 中修改必须用 .value
count.value++;
  • 模板中自动解包:在 <template> 模板中使用时,Vue 会自动帮你读取 .value,所以不需要.value
<template>
  <!-- 模板中直接用,不用 .value -->
  <div>{{ count }}</div> 
</template>

2.2. reactive:对象专属的深层代理

reactive 是专门为处理复杂对象和数组设计的,它利用 Proxy 拦截了对象的所有操作。

  • 复杂对象/表单首选:当你有一堆相关的数据(如表单数据、用户信息)时,用 reactive 代码看起来更清爽,不需要写一堆 .value
import { reactive } from 'vue'
const user = reactive({
  name: 'Alice',
  age: 25,
  address: { city: 'Shanghai' }
});

// 直接修改,不需要 .value,且深层嵌套也是响应式的
user.age = 26
user.address.city = 'Beijing' 
  • 注意限制reactive 不能用于基本类型。如果你尝试 reactive(123),Vue 会抛出警告,且数据不会是响应式的。

2.3. 深度对比:整体替换与重置

在实际开发中,“重置表单”或“刷新数据”是高频操作,这里能最明显看出两者的区别。

  • ref 的灵活性(拥有“安全带”)
  • ref 充当了一个不变的“容器”(盒子),无论你给.value赋值多少次,Vue 都能通过监听这个容器的 .value 属性来捕获变化。
const initialForm = { name: '', email: '', age: null };
const formState = ref({ ...initialForm }); // 初始值

const resetForm = () => {
  // ✅ 直接把初始值对象重新赋值给 .value
  // 干净利落,不管有多少字段,一行代码搞定
  formState.value = { ...initialForm };
};
  • reactive 的局限性(无法换“本体”)
  • reactive 生成的代理对象,你必须小心翼翼地维护那个特定的实例。如果你想直接给它赋值一个新对象,响应式就会断裂。
const formState = reactive({
  name: '',
  email: '',
  age: null
});

const resetForm = () => {
  // ⚠️ 不能直接写 formState = { name: '', email: '', age: null }
  // 这样会丢失响应性
  
  // 所以你不得不手动一个属性一个属性地重置
  formState.name = '';
  formState.email = '';
  formState.age = null;
  
  // 如果表单有 20 个字段,这就非常痛苦
};

三、常见“坑”与避坑指南

3.1. 解构破坏响应性

如果你直接从响应式对象中解构出属性,这个属性会变成普通变量,不再具备响应式能力。

  • 错误示范 (reactive 解构):
const state = reactive({ count: 0 });
const { count } = state; // count 变成普通数字
count++; // 视图不更新
  • 错误示范 (ref 解构):
const state = ref({ count: 0 });
const { count } = state.value; // count 变成普通数字
count++; // 视图不更新
  • 正确解法 (toRefs)
  • toRefs 是解决解构丢失响应性的终极武器。
// 无论 state 是 reactive 还是 ref,这行代码都一样
const { count } = toRefs(state); 

count.value++; // 这样就能触发更新了

3.2. reactive无法整体替换

reactive 返回的是一个代理对象的引用。如果你直接给它赋值一个新对象,就会切断与原代理的联系。

  • 错误示范:
const state = reactive({ count: 0 })
state = { count: 1 } //  报错/无效,响应式断裂
  • 正确解法:
    • 方案 A (修改内部)Object.assign(state, { count: 1 })
    • 方案 B (改用 ref)const state = ref({ count: 0 }); state.value = { count: 1 };

四、总结与最佳实践

  • 基本类型:无脑选 ref
  • 对象/数组,且不需要整体替换:选 reactive,代码更简洁。
  • 对象/数组,需要整体替换:选 ref
  • 需要解构对象:使用 toRefs 包装 reactive 对象。

理解这些差异,能让你在构建 Vue 3 应用时更加得心应手!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐