Vue 3 中 reactive() 与 ref() 的深度对比
Vue 3 Composition API 中的 reactive() 和 ref() 是创建响应式数据的两个核心函数。虽然它们都能让数据变成响应式,但在设计目标、使用方式和内部原理上存在明显的区别。
核心区别概述
reactive()
- 设计目标:专门用于将复杂的对象类型(Object、Array、Map、Set 等)转换成响应式对象
- 特点:深层响应式,修改嵌套属性时也会触发更新
- 参数限制:必须是对象或数组,不能是基本数据类型
ref()
- 设计目标:更加通用,可以接收任何类型的值
- 特点:通过
.value属性包装值来实现响应式 - 参数类型:支持基本数据类型和对象类型
详细对比分析
1. 参数类型与使用方式
reactive() 的使用
javascript
import { reactive } from 'vue';
// ✅ 正确用法 - 对象类型
const state = reactive({
count: 0,
user: { name: 'Alice' }
});
state.count++; // 视图会更新
state.user.name = 'Bob'; // 视图会更新
// ❌ 错误用法 - 基本类型
const count = reactive(0); // 会收到警告,count 不是响应式的ref() 的使用
javascript
import { ref } from 'vue';
// ✅ 用于基本类型
const count = ref(0);
console.log(count.value); // 0
count.value++; // 必须通过 .value 修改
// ✅ 用于对象类型
const state = ref({ name: 'Alice' });
state.value.name = 'Bob'; // 访问和修改也需要 .value2. 访问和修改方式
reactive() - 直接访问
javascript
const state = reactive({
count: 0,
items: ['a', 'b', 'c']
});
// 直接访问,就像普通对象
state.count = 10;
state.items.push('d');
// ⚠️ 解构赋值问题
let { count } = state; // count 失去响应性
count++; // state.count 不会改变,视图不会更新ref() - 通过 .value 访问
javascript
const count = ref(0);
const items = ref(['a', 'b', 'c']);
// JavaScript 中必须使用 .value
count.value = 10;
items.value.push('d');
// 在模板中自动解包,无需 .value
// <template>
// <div>{{ count }}</div> <!-- 不是 {{ count.value }} -->
// </template>3. 模板中的表现
模板自动解包
vue
<template>
<div>
<!-- reactive 对象直接使用 -->
<p>State count: {{ state.count }}</p>
<!-- ref 自动解包,无需 .value -->
<p>Ref count: {{ count }}</p>
<!-- 嵌套在对象中的 ref 不会自动解包 -->
<p>Nested ref: {{ obj.count.value }}</p>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue';
const state = reactive({ count: 0 });
const count = ref(0);
const obj = { count: ref(0) }; // 嵌套的 ref 需要手动 .value
</script>4. 内部实现原理
reactive() 的原理
javascript
// 简化的实现原理
function reactive(target) {
return new Proxy(target, {
get(target, key) {
track(target, key); // 依赖收集
return target[key];
},
set(target, key, value) {
target[key] = value;
trigger(target, key); // 派发更新
return true;
}
});
}- 利用 ES6 的
Proxy对对象进行代理 - 拦截所有操作(get, set, deleteProperty 等)
- 实现深度响应式
ref() 的原理
javascript
// 简化的实现原理
function ref(value) {
return {
get value() {
track(this, 'value'); // 依赖收集
return this._value;
},
set value(newValue) {
this._value = newValue;
trigger(this, 'value'); // 派发更新
},
_value: value
};
}- 创建包装对象,通过
value属性的 getter/setter 实现响应式 - 如果值是对象,内部会调用
reactive()处理
实际应用场景
使用 reactive() 的场景
javascript
// ✅ 表单状态管理
const formState = reactive({
username: '',
email: '',
profile: {
age: null,
city: ''
}
});
// ✅ 列表数据管理
const listState = reactive({
items: [],
loading: false,
total: 0
});使用 ref() 的场景
javascript
// ✅ 简单状态
const loading = ref(false);
const count = ref(0);
// ✅ DOM 元素引用
const inputRef = ref(null);
// ✅ 可能改变类型的数据
const data = ref(null); // 可能是 null 或对象性能考虑
reactive() 的性能特点
- 优势:访问属性时无需
.value,代码更简洁 - 劣势:
- 对整个对象进行代理,内存占用相对较大
- 解构会失去响应性,需要使用
toRefs()等工具函数
ref() 的性能特点
- 优势:
- 更精确的响应式控制
- 不会因解构失去响应性
- 对于基本类型更高效
- 劣势:需要频繁使用
.value,可能影响代码可读性
最佳实践建议
1. 优先使用 ref()
javascript
// ✅ 推荐:统一使用 ref
const count = ref(0);
const user = ref({ name: 'Alice', age: 25 });
const items = ref([]);
// 一致的访问模式
count.value++;
user.value.name = 'Bob';
items.value.push('new item');2. reactive() 适用场景
javascript
// ✅ 复杂的嵌套对象状态
const appState = reactive({
user: {
profile: { name: '', avatar: '' },
settings: { theme: 'light', lang: 'zh' }
},
ui: {
sidebar: { collapsed: false },
modal: { visible: false, title: '' }
}
});3. 混合使用策略
javascript
// ✅ 根据数据特点选择合适的API
const useUserStore = () => {
// 简单状态用 ref
const loading = ref(false);
const error = ref(null);
// 复杂对象用 reactive
const userInfo = reactive({
profile: { name: '', email: '' },
permissions: []
});
return {
loading,
error,
userInfo
};
};对比总结表
| 特性 | reactive() | ref() |
|---|---|---|
| 参数类型 | 只能是对象/数组 | 任意类型 |
| JS 中访问 | 直接访问 (state.prop) | 通过 .value 访问 (count.value) |
| 模板中访问 | 直接访问 (state.prop) | 自动解包 (count) |
| 响应式丢失 | 解构会丢失响应性 | 不会,操作的是 ref 对象本身 |
| 底层原理 | Proxy 代理 | 包装对象 + getter/setter |
| 内存占用 | 相对较大(整个对象代理) | 相对较小(只包装 value) |
| 类型推导 | 较好的 TypeScript 支持 | 优秀的 TypeScript 支持 |
总结
理解 reactive() 和 ref() 的区别是掌握 Vue 3 Composition API 的关键。选择哪个API主要取决于:
- 数据类型:基本类型必须用
ref(),复杂对象两者都可以 - 使用习惯:偏好直接访问选择
reactive(),偏好统一模式选择ref() - 团队约定:保持团队代码风格的一致性
无论选择哪种方式,重要的是理解它们的工作原理,这样才能写出健壮、可维护的 Vue 3 应用程序。