面试官 : “ Vue 中 ref 和 reactive 的区别是什么? ”

46 阅读4分钟

在 Vue3 中,ref 和 reactive 都是用于创建响应式数据的核心 API,但设计初衷、使用场景、语法规则差异显著。以下从核心区别、使用场景、底层原理、常见坑四个维度彻底讲清二者的区别:

一、核心区别(一张表总结)

维度refreactive
适用数据类型基本类型(Number/String/Boolean 等)、单值对象 / 数组复杂类型(Object/Array/Map/Set 等)
响应式实现方式包装成 RefImpl 实例(.value 访问)基于 Proxy 直接代理对象(无.value)
访问方式模板中自动解包(可直接用),JS 中需 .value模板 / JS 中均直接访问属性
解构 / 展开行为解构后仍保持响应式(需用 toRefs 辅助)直接解构会丢失响应式(需用 toRefs
替换整个数据支持(ref.value = 新值不支持(代理对象无法整体替换)
数组 / 集合兼容性对数组操作需 .value(如 refArr.value.push()数组可直接操作(如 reactiveArr.push()
类型推导(TS)更友好(自动推导基本类型)需注意深层嵌套类型推导

二、具体用法 & 场景对比

1. 基本类型:只能用 ref(reactive 无效)

reactive 基于 Proxy,无法代理基本类型,强行使用会丢失响应式ref 会把基本类型包装成带 .value 的响应式对象:

<script setup>
import { ref, reactive } from 'vue';

// ✅ 正确:ref 包装基本类型
const count = ref(0); 
count.value++; // JS 中必须 .value
console.log(count.value); // 1

// ❌ 错误:reactive 无法代理基本类型(无响应式)
const num = reactive(10); 
num++; // 无响应式,页面不更新

// ✅ 模板中 ref 自动解包(无需 .value)
// <template><p>{{ count }}</p></template>
</script>

2. 复杂类型:ref vs reactive 都能用,但场景不同

场景 1:独立对象 / 数组 → 推荐 reactive(更简洁)
<script setup>
import { reactive } from 'vue';

// ✅ reactive 管理复杂对象
const user = reactive({
  name: '张三',
  age: 20,
  hobbies: ['篮球', '游戏']
});

// 直接访问属性,无 .value
user.age++; // 响应式更新
user.hobbies.push('读书'); // 数组直接操作

// ✅ 模板中直接用:<p>{{ user.name }}</p>
</script>
场景 2:需要整体替换对象 → 必须用 ref

reactive 代理的对象无法整体替换(替换后 Proxy 指向新对象,响应式断裂),ref 可通过 .value 整体替换:

<script setup>
import { ref, reactive } from 'vue';

// ✅ ref 支持整体替换
const userRef = ref({ name: '张三' });
userRef.value = { name: '李四' }; // 响应式保留

// ❌ reactive 无法整体替换(丢失响应式)
const userReactive = reactive({ name: '张三' });
userReactive = { name: '李四' }; // 报错:Assignment to constant variable
</script>
场景 3:解构复杂对象 → 需 toRefs 配合

直接解构 reactive 对象会丢失响应式,ref 解构后仍需 .value,需用 toRefs 转为 ref 集合:

<script setup>
import { reactive, ref, toRefs } from 'vue';

// 1. reactive 解构(丢失响应式)
const user = reactive({ name: '张三', age: 20 });
const { name, age } = user; 
name = '李四'; // 无响应式!

// ✅ 用 toRefs 转为 ref 集合(保留响应式)
const userRefs = toRefs(user);
const { name: nameRef, age: ageRef } = userRefs;
nameRef.value = '李四'; // 响应式更新

// 2. ref 解构(本身是 ref 实例,需 .value)
const countRef = ref(0);
const { value: count } = countRef; 
count++; // ❌ 丢失响应式(需直接操作 countRef.value)
</script>

3. 数组操作:ref 需 .value,reactive 更直观

<script setup>
import { ref, reactive } from 'vue';

// ref 数组:JS 中必须 .value 操作
const refArr = ref([1, 2, 3]);
refArr.value.push(4); // ✅ 响应式
refArr.value = [5, 6]; // ✅ 整体替换

// reactive 数组:直接操作
const reactiveArr = reactive([1, 2, 3]);
reactiveArr.push(4); // ✅ 响应式
// reactiveArr = [5,6] // ❌ 无法整体替换
</script>

三、底层原理差异

  1. ref

    • 对基本类型:用 Object.defineProperty 劫持 .value 的 get/set
    • 对复杂类型:内部调用 reactive 生成 Proxy 代理,再包装成 RefImpl 实例;
    • 核心:通过 .value 统一访问入口,解决基本类型响应式问题。
  2. reactive

    • 基于 ES6 Proxy 直接代理对象(而非属性),支持深层嵌套响应式;
    • 只能代理对象 / 数组,无法代理基本类型、null/undefined;
    • 核心:拦截对象的 get/set/deleteProperty 等操作,实现响应式。

四、常见坑 & 最佳实践

坑 1:ref 在 JS 中忘记写 .value

const count = ref(0);
count++; // ❌ 无响应式(实际修改的是 RefImpl 实例,不是内部值)
count.value++; // ✅ 正确

坑 2:reactive 整体替换对象

const obj = reactive({ a: 1 });
obj = { a: 2 }; // ❌ 响应式断裂
// ✅ 替代方案:嵌套一层对象
const wrapper = reactive({ obj: { a: 1 } });
wrapper.obj = { a: 2 }; // 响应式保留

坑 3:解构 reactive 对象丢失响应式

const user = reactive({ name: '张三' });
const { name } = user; 
name = '李四'; // ❌ 无响应式
// ✅ 解决方案:用 toRefs
const { name } = toRefs(user);
name.value = '李四'; // ✅ 响应式

最佳实践

  1. 基本类型 / 单值数据 → 优先 ref;
  2. 复杂对象 / 数组(无需整体替换)  → 优先 reactive;
  3. 需要整体替换的复杂数据 → 用 ref 包装;
  4. 解构响应式对象 → 必用 toRefs
  5. TS 开发:ref 更适合基本类型,reactive 更适合结构化对象。

五、总结

  • ref 是 “万能响应式”:能包一切(基本类型 + 复杂类型),但 JS 中需 .value
  • reactive 是 “对象专属响应式”:语法更简洁(无.value),但仅支持复杂类型,无法整体替换;
  • 核心选择逻辑:看数据类型(基本类型只能 ref)+ 是否需要整体替换(需要则 ref)。