Vue3-toRef、toRefs、toRaw

在 Vue 3 中,toReftoRefstoRaw 都是处理响应式系统的重要工具,它们提供了更灵活的方式来操作 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 接收一个由 reactivereadonly 创建的代理。

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.valuestate.key 双向同步 传递 props 给组合式函数,保持对单个 prop 的响应性
toRefs reactive 对象 包含多个 Ref 的新对象 state 的所有属性都变为 Ref 解构 reactive 对象时保持响应性
toRaw reactive 代理对象 原始的、非响应式对象 绕过响应式追踪的原生对象 性能优化(避免追踪),传递数据给外部库

参考文章

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

相关推荐
用户66006766853913 分钟前
CSS定位全解析:从static到sticky,彻底搞懂布局核心
前端·css
听风说图14 分钟前
Figma Vector Networks: 形状、填充及描边
前端
hanliu200318 分钟前
实训11 ,百度评分
前端
Y***K43423 分钟前
TypeScript模块解析
前端·javascript·typescript
JarvanMo25 分钟前
Xcode 没人想解决的问题:为什么苹果对平庸感到满意
前端
合作小小程序员小小店39 分钟前
web网页开发,在线%餐饮点餐%系统,基于Idea,html,css,jQuery,java,ssm,mysql。
java·前端·数据库·html·intellij-idea·springboot
鹏多多1 小时前
HTML的Video从基础使用到高级实战+兼容的完全指南
前端·javascript·vue.js
晓得迷路了1 小时前
栗子前端技术周刊第 107 期 - Angular v21、pnpm 10.22、React 2025 现状调查...
前端·javascript·angular.js
韩曙亮1 小时前
【Web APIs】JavaScript 事件高级 ③ ( DOM 事件流 | 捕获阶段 | 目标阶段 | 冒泡阶段 )
前端·javascript·web apis·捕获阶段·目标阶段·冒泡阶段·dom 事件流