在 Vue3 的组合式 API 中,ref和reactive是实现响应式数据的核心 API。虽然二者都用于创建响应式对象,但设计初衷、使用场景和内部实现存在显著差异。本文将从原理、用法、差异及最佳实践等方面深入剖析这两个 API。
一、基本概念与原理
1. ref
ref是 Vue3 中用于创建基本类型响应式数据 的 API,本质是对基本类型的包装(wrapper),通过一个包含value属性的对象实现响应式。
实现原理:
- 使用
RefImpl类包装数据,访问value时触发track(依赖收集),修改value时触发trigger(依赖更新) - 对于对象类型,内部会自动调用
reactive进行深层响应式转换
typescript
运行
// 简化实现
class RefImpl<T> {
private _value: T;
constructor(value: T) {
this._value = isObject(value) ? reactive(value) : value;
}
get value() {
track(this, 'value');
return this._value;
}
set value(newVal) {
this._value = isObject(newVal) ? reactive(newVal) : newVal;
trigger(this, 'value');
}
}
2. reactive
reactive用于创建对象类型响应式数据 ,基于 ES6 的Proxy实现,支持深层响应式。
实现原理:
- 通过
Proxy代理对象的所有属性操作 - 对对象的
get操作进行依赖收集,set操作触发更新 - 嵌套对象会被递归转换为
Proxy实例
typescript
运行
// 简化实现
function reactive<T extends object>(target: T): T {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target, key);
return isObject(res) ? reactive(res) : res;
},
set(target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver);
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, key);
}
return result;
}
});
}
二、基本用法对比
1. ref 的使用
javascript
运行
import { ref } from 'vue';
// 基本类型响应式
const count = ref(0);
console.log(count.value); // 0
count.value++; // 触发更新
// 对象类型(内部转为reactive)
const user = ref({ name: '张三', age: 20 });
user.value.name = '李四'; // 响应式更新
// 在模板中使用(自动解包)
// <template>{{ count }} {{ user.name }}</template>
2. reactive 的使用
javascript
运行
import { reactive } from 'vue';
// 对象响应式
const state = reactive({
count: 0,
user: {
name: '张三',
address: {
city: '北京'
}
}
});
state.count++; // 响应式更新
state.user.address.city = '上海'; // 深层响应式
// 数组响应式
const list = reactive([1, 2, 3]);
list.push(4); // 响应式更新
三、核心差异对比
| 特性 | ref | reactive |
|---|---|---|
| 支持类型 | 基本类型 + 对象类型 | 仅对象类型(对象 / 数组 / Map/Set 等) |
| 访问方式 | 需要.value(模板中自动解包) |
直接访问属性 |
| 深层响应式 | 自动实现(对象类型) | 自动实现 |
| 重新赋值 | 支持(ref.value = 新值) |
不支持(会丢失响应式) |
| 解构响应性 | 解构后.value仍保持响应式 |
直接解构会丢失响应式(需用 toRefs) |
| 使用场景 | 基本类型、需要重新赋值的场景 | 复杂对象、不需要整体替换的场景 |
关键差异详解:
1. 重新赋值行为
javascript
运行
// ref支持重新赋值
const num = ref(0);
num.value = 10; // 保持响应式
// reactive不支持整体替换
const obj = reactive({ count: 0 });
obj = { count: 10 }; // 丢失响应式!
2. 解构响应性
javascript
运行
// ref解构
const { count } = { count: ref(0) };
count.value++; // 仍保持响应式
// reactive解构
const state = reactive({ count: 0 });
const { count } = state;
count++; // 非响应式!
// 正确做法:使用toRefs
import { toRefs } from 'vue';
const state = reactive({ count: 0 });
const { count } = toRefs(state);
count.value++; // 响应式
3. 模板中的表现
html
预览
<!-- ref自动解包 -->
<template>
<div>{{ count }}</div> <!-- 无需.value -->
<div>{{ user.name }}</div> <!-- 对象类型也自动解包 -->
</template>
<!-- reactive直接访问 -->
<template>
<div>{{ state.count }}</div>
</template>
四、使用场景选择
优先使用 ref 的场景:
- 基本类型响应式:数字、字符串、布尔值等
- 需要重新赋值的对象 :如
const form = ref({})需要整体替换 - 组合函数返回值:便于解构使用
- DOM 元素引用 :
const el = ref(null)
优先使用 reactive 的场景:
- 复杂对象 / 状态管理:如表单数据、业务状态
- 不需要整体替换的对象:如组件内部的状态对象
- 数组操作 :
const list = reactive([])
组合使用示例:
javascript
运行
import { ref, reactive } from 'vue';
export default {
setup() {
// 基本类型用ref
const userId = ref(1);
const loading = ref(false);
// 复杂状态用reactive
const userInfo = reactive({
name: '',
age: 0,
address: {
city: '',
street: ''
}
});
// 数组用reactive
const hobbies = reactive([]);
return {
userId,
loading,
userInfo,
hobbies
};
}
};
五、常见误区与最佳实践
误区 1:过度使用 reactive
javascript
运行
// 不推荐
const count = reactive({ value: 0 });
count.value++;
// 推荐
const count = ref(0);
count.value++;
误区 2:解构 reactive 对象丢失响应性
javascript
运行
// 错误
const state = reactive({ count: 0 });
const { count } = state;
// 正确
import { toRefs } from 'vue';
const state = reactive({ count: 0 });
const { count } = toRefs(state);
误区 3:ref 对象嵌套 reactive
javascript
运行
// 不推荐(冗余)
const user = ref(reactive({ name: '张三' }));
// 推荐
const user = ref({ name: '张三' });
// 或
const user = reactive({ name: '张三' });
最佳实践:
- 保持一致性:团队约定统一的使用规范
- 按需选择:根据数据类型和使用场景选择合适的 API
- 避免嵌套过深:复杂状态可拆分多个响应式对象
- 配合 toRefs :组合函数返回 reactive 对象时使用
toRefs
六、底层实现差异
ref 的内部结构:
javascript
运行
// ref对象结构
const count = ref(0);
console.log(count);
// {
// __v_isRef: true,
// value: 0
// }
reactive 的内部结构:
javascript
运行
// reactive返回Proxy实例
const state = reactive({ count: 0 });
console.log(state); // Proxy对象
console.log(state.__v_isReactive); // true
响应式判定:
javascript
运行
import { isRef, isReactive } from 'vue';
const count = ref(0);
const state = reactive({});
console.log(isRef(count)); // true
console.log(isReactive(state)); // true
总结
ref和reactive作为 Vue3 响应式系统的基石,各自有明确的设计目标和适用场景:
- ref:更灵活,支持基本类型和对象,适合独立值和需要重新赋值的场景
- reactive:更适合复杂对象和集合类型,提供更自然的访问方式
理解二者的差异和适用场景,能帮助开发者更高效地构建 Vue3 应用,避免常见陷阱,编写出更清晰、可维护的响应式代码。