Vue3-toRef、toRefs、toRaw

在 Vue 3 中,toRef、toRefs 和 toRaw 都是处理响应式系统的重要工具,它们提供了更灵活的方式来操作 reactive 对象。

1.toRef

toRef 用于为 reactive 对象上的单个属性 创建一个响应式的 ref。

toRef 接收两个参数:源对象和属性名。

ts 复制代码
import { reactive, toRef, watchEffect, isRef } from 'vue';

// 1. 定义源对象
const state = reactive({
  foo: 'hello',
  bar: 100,
});

// 2. 创建单个 ref
// const fooRef = toRef(state, 'foo');
// 使用 TS 泛型 (虽然通常会自动推断)
const fooRef = toRef<typeof state, 'foo'>(state, 'foo');

// 3. 验证
console.log(isRef(fooRef)); // true
console.log(fooRef.value); // 'hello'

// 4. 双向绑定特性
// 修改 toRef 创建的 ref 会更新原始对象
fooRef.value = 'world';
console.log(state.foo); // 'world'

// 修改原始对象也会更新 ref
state.foo = 'hello again';
console.log(fooRef.value); // 'hello again'

// 监听变化
watchEffect(() => {
  console.log(`fooRef changed to: ${fooRef.value}`);
});

state.foo = 'Final value'; // watchEffect 会触发

toRef 最核心的用例是在不丢失响应性的情况下传递 props。

举个栗子:从父组件接收参数

  • 当你在 setup 中解构 props 时,你会丢失响应性,因为你得到的是一个普通的 JavaScript 变量。
ts 复制代码
// MyComponent.vue
import { defineProps, watchEffect } from 'vue';

const props = defineProps<{
  count: number;
}>();

// 这样解构会丢失响应性
let { count } = props;
// count 只是一个普通的 number,当 props.count 变化时,它不会更新

watchEffect(() => {
  // 这个 effect 永远不会再次运行,即使 props.count 变了
  console.log(`Count is: ${count}`); 
});
  • 正确示例(使用toRef)
ts 复制代码
// MyComponent.vue
import { defineProps, toRef, watchEffect } from 'vue';

const props = defineProps<{
  count: number;
}>();

// 使用 toRef 将 props.count 转换为一个 ref
const countRef = toRef(props, 'count');

watchEffect(() => {
  // 现在 countRef 是响应式的
  // 当 props.count 变化时,这个 effect 会重新运行
  console.log(`Count is: ${countRef.value}`);
});

2.toRefs

toRefs 用于将一个 reactive 对象的所有属性 转换为一个普通对象,其中每个属性都是一个 ref。

toRefs 接收一个 reactive 对象。

ts 复制代码
import { reactive, toRefs, ToRefs } from 'vue';

interface User {
  name: string;
  age: number;
}

const state = reactive<User>({
  name: 'Bob',
  age: 25,
});

// 1. 转换整个对象
// stateAsRefs 的类型是 { name: Ref<string>, age: Ref<number> }
const stateAsRefs = toRefs(state);

// 2. 访问
console.log(stateAsRefs.name.value); // 'Bob'
console.log(stateAsRefs.age.value); // 25

// 3. 同样具有双向绑定特性
stateAsRefs.name.value = 'Charlie';
console.log(state.name); // 'Charlie'

state.age = 26;
console.log(stateAsRefs.age.value); // 26

举个栗子:toRefs 最常见的场景是在 setup 函数中返回响应式对象时,允许你在模板中直接使用属性,或者在解构时保持响应性。

  • setup 中返回对象 如果你直接返回一个 reactive 对象,你必须在模板中以 state.name 的方式访问它。
ts 复制代码
import { reactive, toRefs, toRef, toRaw } from "vue";

interface User {
  name: string;
  age: number;
}

const user = reactive<User>({
  name: "YaeZed",
  age: 25,
});
// 如果想直接结构reactive,会丢失响应性
const { name, age } = user;

// 模板中: <p>{{ user.name }}</p> <p>{{ user.age }}</p>

使用 toRefs,你可以"展开" (spread) 这个对象,使其属性在模板中顶层可用。

ts 复制代码
import { reactive, toRefs, toRef, toRaw } from "vue";

interface User {
  name: string;
  age: number;
}

const user = reactive<User>({
  name: "YaeZed",
  age: 25,
});
// 使用 toRefs 解构,将所有属性转换为ref
const { name, age } = toRefs(user);

// 模板中: <p>{{ name }}</p> <p>{{ age }}</p>
// (模板会自动解包 .value)
  • 解构 reactive 对象 与 toRef 类似,如果你想解构一个 reactive 对象同时保持响应性,必须使用 toRefs。
ts 复制代码
import { reactive, toRefs, watchEffect } from 'vue';

const state = reactive({
  x: 1,
  y: 2,
});

// ❌ 错误:丢失响应性
// const { x, y } = state; 
// x 和 y 只是数字 1 和 2

// ✅ 正确:使用 toRefs
const { x, y } = toRefs(state);
// x 和 y 现在是 Ref<number>

watchEffect(() => {
  console.log(`x: ${x.value}, y: ${y.value}`);
});

// 修改 state 会触发 watchEffect
state.x = 10;

3.toRaw

toRaw 用于从 Vue 的响应式代理 (Proxy) 中获取原始的、非响应式的目标对象。

toRaw 接收一个由 reactive 或 readonly 创建的代理。

ts 复制代码
import { reactive, toRaw, isReactive } from 'vue';

interface Data {
  count: number;
}

const data: Data = { count: 0 };
const state = reactive<Data>(data);

// 1. 获取原始对象
const rawData = toRaw(state);

// 2. 验证
console.log(isReactive(state)); // true
console.log(isReactive(rawData)); // false

// 3. 比较
console.log(rawData === data); // true (它们是同一个原始对象)
console.log(state === data); // false (state 是一个 Proxy)

// 4. 修改
// 修改原始对象 (raw) 仍然会通过引用改变代理对象 (state)
rawData.count = 1;
console.log(state.count); // 1

// **关键区别**:
// 通过 rawData 修改属性 *不会* 触发 Vue 的响应式系统
// (例如,依赖 state.count 的 watcher 不会运行,组件也不会重新渲染)

// 只有通过 state (代理) 修改才会触发更新
state.count = 2; // 这将触发更新

toRaw 应该谨慎使用,因为它会脱离 Vue 的响应式追踪。

  • 性能优化 (临时读取) : 当你需要读取 reactive 对象的数据,但你确定 这个读取操作不需要被追踪(即不希望它触发 watchEffect 或组件更新)时。

  • 传递给外部库 : 某些第三方库(如图表库)可能不应该接收 Vue 的 Proxy 对象。它们可能试图直接修改对象,或者 Vue 的代理可能会导致性能问题或意外行为。在这种情况下,你可以使用 toRaw 传递原始数据。

  • 调试 : 在浏览器控制台中检查一个 reactive 对象时,它会被显示为 Proxy。使用 toRaw(state) 可以让你看到它背后的原始数据结构。

4.总结

API 输入 输出 核心特点 使用场景
toRef reactive 对象, 属性名 (key) 单个 Ref ref.value 与 state.key 双向同步 传递 props 给组合式函数,保持对单个 prop 的响应性
toRefs reactive 对象 包含多个 Ref 的新对象 state 的所有属性都变为 Ref 解构 reactive 对象时保持响应性
toRaw reactive 代理对象 原始的、非响应式对象 绕过响应式追踪的原生对象 性能优化(避免追踪),传递数据给外部库

参考文章

小满zs 学习Vue3 第八章(认识to系列全家桶)xiaoman.blog.csdn.net/article/det...

相关推荐
高级程序源3 小时前
django校园寻物通失物招领平台32261-计算机课程设计、毕业设计
vue.js·python·mysql·小程序·django·flask·课程设计
WeiXin_DZbishe3 小时前
基于springboot宠物领养系统-计算机毕设 附源码【课程设计】94217
vue.js·spring boot·python·django·php·课程设计
架构技术专栏4 小时前
Agent 工具调用:从工具声明到执行反馈
前端·人工智能·后端
别动我齐刘海4 小时前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
志尊宝5 小时前
Vue3 零基础每日笔记(071):暗黑模式与主题切换——CSS 变量 + Element Plus 暗黑方案
前端·css·笔记·vue·html·前端开发·软件开发
WeiXin_DZbishe6 小时前
基于springboot城市公交实时运营与调度系统-计算机毕设 附源码【课程设计】66173
java·vue.js·spring boot·后端·python·php·课程设计
刘发财6 小时前
前端如何生成10000页PDF文件?
前端·javascript·开源
WeiXin_DZbishe6 小时前
基于Springboot+AI的kuall音乐广场的设计与实现-计算机毕设 附源码【课程设计】12715
vue.js·人工智能·spring boot·后端·python·php·课程设计
A接拉起0076 小时前
2026 年 JavaScript 开发者需要了解的知识
前端·javascript·后端
_馒头6 小时前
pixiJs游戏开发实践 - 跳棋小游戏
javascript·vue.js