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...

相关推荐
10岁的博客5 分钟前
Chrome 扩展安全攻防实录:从权限模型到真实漏洞复盘
前端·chrome·安全
QianhangQianping19 分钟前
WebSocket实时推送+ HTTP 定时轮询:一块大屏两套取数怎么不打架
前端·websocket·网络协议·http·低代码·架构·数据可视化
小郎君65825 分钟前
(六)el-cascader 的展示值为什么不能直接 join:emitPath、路径还原与多选去重
vue.js·算法
Eider199826 分钟前
Python基础知识之并发相关-2
开发语言·前端·python
用户693717500138429 分钟前
AI 能写代码,但工程架构还是应该由人来定
前端·后端
Hilaku36 分钟前
前端的技术判断力是怎么练出来的?
前端·javascript·程序员
悠仁さん39 分钟前
【C++】set map 使用
java·前端·c++
用户0936717554142 分钟前
用 4500 行 TypeScript 复刻了童年方格纸上的海战棋
前端·vue.js
CCIE-Yasuo1 小时前
网络安全-学习web开发-基础概念、常用标签
前端·学习
凉介川1 小时前
给 DeepSeek Harness 写插件:按扩展名接管 Windows 右键打开
前端