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

相关推荐
Cherry的跨界思维1 天前
【AI测试全栈:Vue核心】22、从零到一:Vue3+ECharts构建企业级AI测试可视化仪表盘项目实战
vue.js·人工智能·echarts·vue3·ai全栈·测试全栈·ai测试全栈
ssshooter1 天前
复古话题:Vue2 的空格间距切换到 Vite 后消失了
前端·vue.js·面试
IamZJT_1 天前
拒绝做 AI 的“饲养员” ❌:前端程序员在 AI 时代的生存与进化指南 🚀
前端·ai编程
MM_MS1 天前
Halcon控制语句
java·大数据·前端·数据库·人工智能·算法·视觉检测
程序员Agions1 天前
程序员武学修炼手册(二):进阶篇——小有所成,从能跑就行到知其所以然
前端·程序员
小画家~1 天前
第四十六: channel 高级使用
java·前端·数据库
小贵子的博客1 天前
Ant Design Vue <a-table>
前端·javascript·vue.js·anti-design-vue
m0_502724951 天前
vue动态设置背景图片后显示异常
前端·css
console.log('npc')1 天前
vue2中子组件父组件的修改参数
开发语言·前端·javascript
奋斗吧程序媛1 天前
vue3 Study(1)
前端·javascript·vue.js