一、基础概念与本质区别
1. ref 的本质
- 定义:通过
ref()函数创建响应式引用,可接收任意类型数据(基本类型/对象/数组)。 - 实现原理:将数据包裹在
{ value: 数据 }的对象中,通过Proxy监听value的访问与修改。 - 使用场景:基本类型数据(如
number、string),或需要明确声明单个响应式变量时。
示例:
import { ref } from 'vue';
const count = ref(0);
console.log(count.value); // 0
count.value = 1; // 修改值
2. reactive 的本质
- 定义:通过
reactive()函数创建响应式对象,只能接收对象类型数据(对象/数组/Map 等)。 - 实现原理:直接对原始对象创建
Proxy代理,监听所有属性的读写操作。 - 使用场景:对象或数组等复杂数据结构,需要批量处理响应式状态时。
示例:
import { reactive } from 'vue';
const state = reactive({ count: 0 });
console.log(state.count); // 0
state.count = 1; // 修改值
二、核心差异对比(面试高频)
| 维度 | ref | reactive |
|---|---|---|
| 数据类型 | 支持基本类型和对象类型 | 仅支持对象类型(需包裹对象) |
| 响应式原理 | 包裹为 { value } 的 Proxy 对象 | 直接代理原始对象的所有属性 |
| 访问方式 | 通过 .value 访问和修改 | 直接通过属性访问(如 state.foo) |
| 解构丢失响应式 | 解构后需用 toRefs 保持响应式 | 解构即丢失响应式(需用 toRef) |
| 性能消耗 | 每次访问需通过 .value,略高 | 直接访问属性,性能更优 |
三、面试必问场景与解决方案
1. 为什么 ref 可以处理基本类型,而 reactive 不行?
- 原因:
reactive基于Proxy,只能代理对象类型,无法直接监听基本类型的变化。ref通过包裹成{ value }的对象,将基本类型转为对象类型,从而实现响应式。
2. 解构 ref/reactive 时如何保持响应式?
- 场景示例:
const state = reactive({ count: 0, name: 'Vue' }); // 错误:解构会丢失响应式 const { count } = state; count++; // 无法触发视图更新 const num = ref(0); // 错误:解构直接获取 value,丢失响应式 const { value } = num; value++; // 无效 - 解决方案:
- reactive 解构:使用
toRefs或toRefimport { toRefs } from 'vue'; const state = reactive({ count: 0 }); const { count } = toRefs(state); // count 是 ref 对象,保留响应式 - ref 解构:直接使用
{ count } = num会丢失响应式,需通过value访问,或用toRefs包裹后解构。
- reactive 解构:使用
3. 数组操作时 ref 与 reactive 的差异
- reactive 数组:
- 直接通过索引修改(如
arr[0] = 1)不会触发更新,需用Vue.set或数组原生方法(push/splice等)。
- 直接通过索引修改(如
- ref 数组:
- 本质是
ref({ value: 数组 }),通过arr.value[0] = 1修改会触发更新,或使用update方法:const arr = ref([1, 2, 3]); arr.value[0] = 0; // 有效 arr.value.push(4); // 有效
- 本质是
四、进阶应用与最佳实践
1. 组合式 API 中的选择策略
- 单个变量:优先用
ref(如计数器、布尔值)。 - 对象/数组:优先用
reactive(如表单数据、列表)。 - 混合场景:用
ref包裹对象,避免频繁toRefs:const form = ref({ name: '张三', age: 18 }); // 访问:form.value.name
2. 与 setup/suspense 的配合
ref支持unref语法糖(自动解包value):<template> <div>{{ count }}</div> <!-- 等价于 count.value,需在 setup 中用 unref(count) 或在模板中直接使用 --> </template> <script> import { ref, unref } from 'vue'; setup() { const count = ref(0); // 模板中可直接用 count,内部自动调用 unref(count) return { count }; } </script>
3. 性能优化场景
- 大型对象:若对象属性很多且部分无需响应式,用
reactive比ref更高效(减少Proxy包裹层级)。 - 避免冗余响应式:基本类型用
ref,复杂对象用reactive,避免过度包裹。
五、问题
1. 什么时候用 ref,什么时候用 reactive?
- 处理基本类型(如数字、字符串)时用
ref,因其能将基本类型转为响应式对象。 - 处理对象或数组时用
reactive,直接代理原始数据更高效。 - 当需要解构响应式数据并保持响应式时,
ref配合toRefs更便捷(如从组合函数返回状态时)。
2. ref 和 reactive 的响应式丢失场景及解决方法?
- reactive 解构丢失:解构对象属性会脱离
Proxy代理,需用toRefs或toRef转换。 - ref 解构丢失:直接解构
ref对象会获取value,失去响应式,需通过value访问或用toRefs包裹。 - 数组索引修改:
reactive数组直接修改索引不触发更新,需用Vue.set或数组方法;ref数组需通过value修改。