跳到主要内容

深入理解为什么需要 ref、toRef、toRefs

面试速答(30 秒版 TL;DR)

  • ref:把一个值(尤其是基本类型)包装成响应式引用,读写用 .value
  • reactive:把对象变成响应式代理(Proxy)。
  • toRef:把一个响应式对象上的某个属性“映射成 ref”,保持与源属性联动。
  • toRefs:把响应式对象的每个属性都转成 ref,常用于 解构后仍保持响应式
  • 为什么需要:因为直接解构 reactive 会丢失响应式关联,toRef(s) 用来“保住响应式链接”。

先把核心坑说清楚:解构会断开响应式

import { reactive, watchEffect } from 'vue';

const state = reactive({ count: 0 });
const { count } = state; // 这里 count 是普通 number,不是响应式

watchEffect(() => {
// 这里不会因为 state.count 改变而重新执行
console.log('count =', count);
});

state.count++;

原因:reactive 的响应式建立在 Proxy 的 getter 上;你解构取出来的是一个“当时的值”,后续不再走 Proxy getter,自然也就不再被 track。


ref:把值变成可追踪的响应式引用

import { ref } from 'vue';

const count = ref(0);
count.value++;

什么时候用 ref

  • 基本类型(number/string/boolean)优先用 ref
  • 需要“可整体替换”的对象也常用 ref(例如 const user = ref<User | null>(null))。

toRef:把对象的某个属性变成 ref(保持联动)

import { reactive, toRef, watchEffect } from 'vue';

const state = reactive({ count: 0 });
const count = toRef(state, 'count');

watchEffect(() => {
console.log(count.value); // 会随 state.count 变化
});

state.count++;
count.value++;

适用场景:

  • 你只想“拿出一个字段”给别处用,但还要保持和源对象同步。

toRefs:批量把对象属性转成 ref(常用于 composable 返回值)

import { reactive, toRefs } from 'vue';

const state = reactive({ a: 1, b: 2 });
const { a, b } = toRefs(state);

典型场景:

  • useXxx() 里维护一个 reactive 状态,然后 return toRefs(state),让调用方可以解构使用,同时保持响应式。

常见追问

Q1:props 里为什么也常用 toRef/toRefs?

因为 props 是响应式对象(Vue 3),但你一旦解构就可能丢响应式。常见写法:

const props = defineProps<{ id: string }>();
const id = toRef(props, 'id');

Q2:ref 和 reactive 怎么选?

  • 单值、需要整体替换:ref
  • 多字段对象、喜欢 state.x 形式:reactive
  • 需要解构但保响应式:toRef/toRefs