先记住这个答案
toRef(state, key) 创建指向对象属性的 ref,读取和写入围绕原属性进行;toRefs(state) 把调用时已有的可枚举属性转换成一组这样的 ref,适合解构后仍需保持字段联系的接口。ref(state.field) 则先读取当前字段值,再创建独立容器,原始类型字段不会因此双向连接。Vue 3.3 起 toRef 还支持值、ref 和 getter 的规范化签名,要与对象属性签名区分。
- 单字段或尚未存在的可选字段可用属性形式 toRef
- toRefs 处理当时已有的可枚举字段,不自动扩展未来字段
- ref 当前属性值通常是新容器,不能代替属性连接
为什么直接解构原始类型字段会断开联系
页面状态包含 page 和 pageSize,普通解构会把这次读取到的数字放进局部变量。随后修改 state.page,并不会自动让那个数字变量更新。通过 toRefs 解构得到的是字段 ref,读取 page.value 时仍然经由原对象属性,因此可以保留与源字段的联系。
这不意味着解构任何对象都会失去响应式。如果解构出来的本来就是一个 ref 容器,或者仍然是响应式子对象引用,后续访问要按实际类型判断。面试中应明确是原始类型属性的普通值复制,而不是把“解构会失效”扩展成所有 JavaScript 解构的统一规则。
单个可选字段与批量现有字段分别处理
toRefs 只处理调用时能枚举到的字段,不会在以后对象新增属性时自动给返回对象增加新 ref。表单中稍后才出现的可选字段,可以明确使用 toRef(state, "note") 建立访问入口。源对象需要具有响应式能力,不能期待工具把任意普通对象的直接修改都转换成响应式通知。
下面代码对照字段 ref、批量 ref 与独立容器。修改源 page 后,两种字段入口读取新值,独立容器仍保存最初读取的数字;给可选字段 ref 写值,则会写到源对象对应属性。示例展示数据联系,不执行网络或界面操作。
import { reactive, ref, toRef, toRefs } from 'vue';
const state = reactive<{ page: number; note?: string }>({ page: 1 });
const linked = toRef(state, 'page');
const fields = toRefs(state);
const copied = ref(state.page);
const note = toRef(state, 'note');
state.page = 4;
note.value = 'review';
const result = [linked.value, fields.page.value, copied.value, state.note];Vue 3 中 result 依次为 4、4、1、review。toRefs 创建时 note 不存在,所以 fields 不会自动增加该字段;单独建立的 note ref 可以继续访问源属性。
getter 规范化不是另一种写入通道
在支持规范化签名的版本中,toRef(existingRef) 可以直接保留已有 ref;toRef(() => props.id) 提供只读读取入口,每次访问 value 会调用 getter。它有助于把多种动态来源统一交给组合函数,但不像 computed 那样提供基于依赖的计算结果缓存。
对 props 使用属性 ref 或 getter ref,不会改变单向数据流约束。子组件需要更新父层字段时,可以发送明确事件,或按组件模型协议构造可写计算入口。不能通过 toRef 绕过只读限制,也不要把工具生成的包装当成一份独立草稿;真正草稿应明确复制、保存与重置的规则。
容易答错的地方
- toRefs 会深拷贝对象,改 ref 不影响原数据
- 它建立的是字段访问联系,并非独立副本。需要编辑草稿与原数据隔离时,应采用适合数据类型的复制与提交流程,不能靠 toRefs 获得隔离。
- 以后新增的字段会自动出现在 toRefs 返回对象中
- 批量转换发生在调用时,返回结果不是自动随源对象字段集合扩张的镜像。可选字段应显式建立属性 ref,或在接口设计中提前声明需要的入口。
面试官还会怎么问?
ref(state.object) 与原对象完全无关吗?
不能这样概括。它会创建新的 ref 容器,但传入对象仍可能共享同一底层对象或代理引用;这里原始类型快照与对象引用共享需要分别判断。
toRef getter 可以用来缓存昂贵计算吗?
getter 规范化主要提供只读访问入口,不具备 computed 的依赖缓存语义。昂贵纯计算需要缓存时应考虑 computed,并正确表达它读取的响应式来源。
组合函数返回 reactive 还是多个 ref 更好?
取决于希望调用方怎样使用。返回普通对象中的多个 ref 便于解构保留容器;返回 reactive 对象便于直接访问字段。接口应保持一致并说明整体替换与只读约定。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。