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