Vue3 ref和reactive对比解析:深入理解响应式数据
在 Vue 3 的 Composition API 中,
ref和reactive是创建响应式状态的两个核心函数。它们都旨在将普通 JavaScript 数据转换为响应式数据,以便在数据变化时自动触发视图更新。然而,它们在处理数据类型、访问方式和底层机制上存在显著差异。理解这些差异对于有效地使用 Composition API 至关重要。
核心思想:ref 用于处理原始值和对象,通过 .value 访问其内部值,而 reactive 专门用于处理对象,直接访问对象的属性,且底层基于 Proxy 实现。
一、ref:处理原始值和对象
ref 函数接受一个内部值(inner value),并返回一个响应式的 ref 对象。这个 ref 对象只有一个 value 属性,用来指向内部值。
1.1 定义和用法
- 定义:
ref可以接收任何类型的值作为参数:原始值 (string, number, boolean, null, undefined, Symbol) 或对象 (Object, Array)。 - 访问:在 JavaScript 中访问
ref对象时,需要通过其.value属性来获取或修改其内部值。在 Vue 的模板中,ref会自动解包(unwrap),因此可以直接访问,无需.value。
示例:
1 | <template> |
1.2 ref 的特点
- 内部值可变:
ref封装的内部值可以通过ref.value随时更改。 - 模板自动解包:在模板中,如果
ref对象是顶层属性,Vue 会自动解包其.value。 - 对象被深度响应式化:如果
ref接收的是一个对象,Vue 会自动通过reactive将其转换为深层响应式对象。这意味着user.value.name = 'Bob'这样的操作也能触发更新。 unref():可以用来判断一个值是否是ref对象,如果是,则返回其内部值,否则返回其自身。1
2
3
4
5
6import { ref, unref } from 'vue';
const numRef = ref(10);
const num = 20;
console.log(unref(numRef)); // 10
console.log(unref(num)); // 20isRef():判断一个值是否为ref对象。1
2
3
4
5
6import { ref, isRef } from 'vue';
const numRef = ref(10);
const num = 20;
console.log(isRef(numRef)); // true
console.log(isRef(num)); // falsetoRef()/toRefs():这两个工具函数通常用于将reactive对象中的属性转换为ref,以便解构或传递给子组件时保持响应性。
二、reactive:处理对象
reactive 函数接收一个普通的 JavaScript 对象(或数组),并返回该对象的响应式代理(Proxy)。
2.1 定义和用法
- 定义:
reactive只能接收对象类型 (Object, Array, Map, Set)。如果传入原始值,reactive会直接返回该原始值,并且不具备响应性。 - 访问:直接通过对象的属性名访问和修改,就像普通 JavaScript 对象一样,无需
.value。
示例:
1 | <template> |
2.2 reactive 的特点
- 深层响应式:
reactive会将其对象以及所有嵌套的对象都转换为响应式代理。 - 基于 Proxy:
reactive的实现是基于 ES6 的Proxy对象,它能够拦截对对象的各种操作(如属性访问、赋值、删除等)。 - 解构丢失响应性:对
reactive对象进行解构 (const { name } = user;) 会导致解构出的变量失去响应性,因为它们不再是Proxy对象的属性。为了解决这个问题,可以使用1
2
3
4const state = reactive({ count: 0 });
let { count } = state;
count++; // count 只是一个普通数字,state.count 不变
console.log(state.count); // 0toRefs或toRef。 isReactive():判断一个值是否为reactive对象。1
2
3
4
5
6import { reactive, isReactive } from 'vue';
const state = reactive({ count: 0 });
const obj = { count: 0 };
console.log(isReactive(state)); // true
console.log(isReactive(obj)); // falsemarkRaw():将一个对象标记为“原始的”,使其永远不会被转换为响应式对象。
三、ref 和 reactive 的对比总结
| 特性 | ref |
reactive |
|---|---|---|
| 接受类型 | 原始值和对象 (Object, Array 等) | 只能是对象 (Object, Array, Map, Set) |
| 访问方式 | 在 JS 中通过 .value 访问;在模板中自动解包 |
直接通过属性名访问;在模板中也直接访问 |
| 底层实现 | 内部值是一个 Proxy 对象 (如果内部值是对象),外部是一个普通对象加上 .value 属性 |
直接返回一个 Proxy 对象 |
| 解构问题 | 无解构问题,解构 ref 仍需 .value 或 toRefs 转换 |
直接解构会丢失响应性,需要配合 toRefs 使用 |
| 深层响应式 | 如果内部是对象,则自动深层响应式化 | 默认深层响应式化 |
| 用途建议 | 推荐用于封装原始值,或单个复杂对象(当需要将整个对象替换时) | 推荐用于封装多个相关联的属性的对象(如表单数据),或数据集合 |
四、选择 ref 还是 reactive?
选择 ref 还是 reactive 主要取决于你想要封装的数据类型以及你的开发习惯。
处理原始值 (Primitive Values):
- 只能使用
ref。reactive对原始值无效。
- 只能使用
处理对象 (Objects):
- 推荐使用
reactive:当你有一个包含多个属性的对象,并且你希望以更自然的方式(无需.value)访问这些属性时,reactive是更好的选择。1
2
3
4
5
6const form = reactive({
username: '',
password: '',
rememberMe: false
});
// 访问:form.username - 特殊情况使用
ref封装对象:- 当你需要完全替换整个对象实例时,
ref封装对象会更方便。例如,从后端获取新数据后,需要用新对象替换现有对象:如果使用1
2
3
4
5
6const userInfo = ref({ name: 'Old', age: 0 });
// ...
async function fetchNewUser() {
const newUserData = await api.getUser(); // 假设返回 { name: 'New', age: 1 }
userInfo.value = newUserData; // 替换整个对象
}reactive,直接userInfo = newUserData会破坏响应性(因为userInfo只是一个代理对象,重新赋值会使其指向新的普通对象)。你需要逐个属性赋值:1
2
3
4
5
6const userInfo = reactive({ name: 'Old', age: 0 });
// ...
async function fetchNewUser() {
const newUserData = await api.getUser();
Object.assign(userInfo, newUserData); // 逐个赋值,保留代理
} - 当你需要将对象作为参数传递给函数或子组件,并希望保持其响应性而不受解构影响时,
ref封装的对象可以避免reactive的解构问题(虽然reactive可以通过toRefs解决)。
- 当你需要完全替换整个对象实例时,
- 推荐使用
统一风格:
- 全部使用
ref:有些开发者倾向于所有响应式数据都使用ref,以保持代码风格一致,并且始终通过.value访问。这可以避免忘记.value的情况。1
2const count = ref(0);
const user = ref({ name: 'Alice', age: 30 }); // 封装对象 - 混用
ref和reactive:Vue 官方鼓励根据数据类型合理混用。原始值用ref,对象用reactive。
- 全部使用
总结性建议:
- 默认使用
ref,因为它对原始值和对象都适用,且模板中自动解包,使用起来更“通用”。 - 当处理结构复杂的对象,且需要通过多个属性来操作时,考虑使用
reactive,因为它提供更自然的 JavaScript 对象操作体验。 - 理解
reactive的解构问题,并在需要时使用toRefs辅助。
五、实际应用场景示例
5.1 reactive 封装表单数据
1 | <template> |
5.2 ref 封装单个状态和通过 toRefs 处理 reactive 解构
1 | <template> |
六、总结
ref 和 reactive 都是 Vue 3 Composition API 中强大的响应式工具。ref 提供了一个通用的封装,能够处理所有类型的数据,通过 .value 访问,并在模板中自动解包。reactive 则专为对象设计,提供更直接的属性访问体验,但需注意解构带来的响应性丢失问题,并可配合 toRefs 解决。在实际开发中,开发者可以根据具体的数据类型、操作习惯以及是否需要替换整个对象等因素,灵活选择使用它们,或者将它们结合起来使用。理解它们的差异和适用场景,有助于写出更高效、更易维护的 Vue 3 应用。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 1024 维度!
