Vue中的 ref、toRef 和 toRefs 有什么区别

374 阅读3分钟

1. ref :创建独立的响应式引用

  • 用途:为基本类型(stringnumberboolean)或对象创建一个响应式引用。

核心特性

  • 返回值:返回一个包含 .value 属性的响应式对象。
  • 响应式原理:通过 Object.defineProperty 或 Proxy 实现值的跟踪。
  • 自动解包:在模板中使用时无需 .value,但在 JavaScript 逻辑中需要。
import { ref } from 'vue';
const count = ref(0);      // 基本类型
const user = ref({ name: 'Alice' }); // 对象
count.value++;             // 修改值
user.value.name = 'Bob';   // 修改对象属性(自动触发响应)

2. toRef :将响应式对象的属性转为单个ref

  • 用途:从 reactive 创建的响应式对象中提取某个属性,生成一个与之保持连接的 ref

核心特性

  • 返回值:返回一个与源属性 保持响应式连接ref
  • 响应式原理:直接代理原对象的属性,修改 ref 或原对象会互相影响。

适用场景:需要将某个属性单独传递并保持响应性。

import { reactive, toRef } from 'vue';

const state = reactive({ name: 'Alice', age: 30 });
const nameRef = toRef(state, 'name');

nameRef.value = 'Bob'; // 修改 ref 值
console.log(state.name); // 'Bob'(源对象同步更新)

3. toRefs :将响应式对象的所有属性转为 ref

  • 用途:将 reactive 对象转换为普通对象,但每个属性都是 ref,保持响应性。

核心特性

  • 返回值:返回一个普通对象,每个属性都是与原对象属性绑定的 ref

  • 响应式原理:为每个属性创建 toRef,确保解构后仍保持响应性。

  • 适用场景:在返回响应式对象时,结合解构语法保持响应性(如从 setup 返回数据)。

import { reactive, toRefs } from 'vue';
const state = reactive({ name: 'Alice', age: 30 });
const { name, age } = toRefs(state); // 解构为 ref

name.value = 'Bob'; // 修改 ref
console.log(state.name); // 'Bob'(源对象同步更新)

4. 对比表格

特性reftoReftoRefs
用途创建新的响应式引用将响应式对象的某个属性转为 ref将响应式对象的所有属性转为 ref
返回值单个 ref 对象单个 ref 对象包含多个 ref 的普通对象
数据源任意值(基本类型或对象)必须来自 reactive 对象必须来自 reactive 对象
响应性连接独立响应式引用与原对象属性保持双向绑定与原对象所有属性保持双向绑定
解构支持不直接支持解构不直接支持解构支持解构并保持响应性
自动解包模板中自动解包(无需 .value需手动使用 .value需手动使用 .value

5. 常见误区与最佳实践

1. ref toRef 的误用

  • 错误示例
const state = reactive({ count: 0 });
const countRef = ref(state.count); // ❌ 失去响应性!

ref(state.count) 会复制 state.count 的初始值,但后续修改 state.count 不会影响 countRef

  • 正确做法:使用 toRef 保持连接:
const countRef = toRef(state, 'count'); // ✅

2. 解构响应式对象

  • 错误示例
const state = reactive({ count: 0 });
const { count } = state; // ❌ 解构后失去响应性!
  • 正确做法:使用 toRefs
const { count } = toRefs(state); // ✅

3. 组合式函数的返回值

  • 最佳实践:在组合式函数中返回 toRefs(reactiveObject),确保调用方可安全解构:
function useCounter() {
  const state = reactive({ count: 0 });
  const increment = () => { state.count++ };
  return { ...toRefs(state), increment }; // ✅
}

6. 总结

  • 避免滥用 toRefs:如果不需要解构整个对象,优先使用 toRef 按需提取属性。
  • ref reactive 的选择:简单数据用 ref,复杂对象用 reactive
  • 响应性丢失:直接解构 reactive 对象会失去响应性,必须通过 toRefs 解构。