先记住这个答案
组合式函数可以根据需求接收普通值、ref 或 getter,并用 toValue 统一读取它们。需要随来源变化更新时,应把读取放在 computed、watch getter 或 watchEffect 的同步追踪过程中;在函数入口只读取一次,得到的仍可能只是快照。普通值本身没有后续变化入口,调用方若想追踪 prop,应传 ref 或访问该 prop 的 getter。接口还要说明函数参数究竟是取值 getter 还是要作为数据保存的回调。
- toValue 支持值、ref 和 getter,不能只在入口解包一次
- 依赖读取要发生在实际响应式追踪过程内
- 传入普通字段值可能失去来源,getter 保留重新读取能力
为什么统一解包仍可能不更新
如果在组合式函数开头执行 const value = toValue(source),随后 computed 只读取 value,最初的响应式读取已经发生在追踪过程之外。computed 不能仅凭一个数字知道它原来来自哪个 ref。应把 toValue(source) 放到真正计算的 getter 中,让每次计算都能够读取当前来源。
下面的倍增函数接受三种输入。普通数字产生固定结果,ref 更新会使结果失效,getter 可以选择另一个响应式对象中的字段。接口的灵活性来自保留来源入口,而不是用任意包装把已经丢失的信息重新创造出来。
import { computed, toValue } from 'vue';
import type { MaybeRefOrGetter } from 'vue';
function useDouble(source: MaybeRefOrGetter<number>) {
const doubled = computed(() => toValue(source) * 2);
return { doubled };
}传入 ref(2) 后把它更新为 3,doubled 应从 4 变为 6;传入当前的 ref.value 则只提供那个数字快照。getter 的内部读取也在 computed 的追踪范围内。
props 和对象字段应该怎样传
调用 useDouble(props.count) 时通常已经取出了当前原始值;需要跟随父层更新,可以传 () => props.count。即使某些 Vue 3.5 script setup 解构访问有编译支持,普通函数边界仍应清楚表达需要一个可重新求值的来源,不能只依赖变量名字看起来像响应式。
unref 主要解包 ref,不会按相同规则执行普通 getter;toValue 则会调用函数形式的输入。因此当业务数据本身是一个回调函数时,不应含糊地套用同一个参数协议,可以改用对象字段或不同入口明确区分。
副作用与纯派生的读取位置不同
纯计算适合 computed;发请求或订阅外部资源时,可以在 watch 或 watchEffect 中同步读出当前来源,再执行异步工作。watchEffect 在 await 之后读取到的依赖通常不会被同一轮同步追踪收集,应先确定请求键并注册清理。
参数变化后旧请求如何失效、空值时是否停止工作,也属于组合式函数契约。toValue 只统一取值,不会自动提供取消、竞态保护或缓存。测试应覆盖三种输入、来源替换以及无效参数,确认灵活参数没有隐藏不同调用方式的行为差异。
容易答错的地方
- 入口调用一次 toValue 就永久绑定来源
- 它返回当前值,是否持续追踪取决于之后在哪里重新读取来源。排查“Vue 组合式函数 ref getter toValue 参数追踪”时还要核对输入、版本和执行顺序,并用反例确认修复后的边界。
- unref 与 toValue 对函数完全一样
- toValue 会执行 getter 形式输入,unref 不会把普通函数自动当作 getter 求值。
面试官还会怎么问?
参数必须支持全部三种形式吗?
不必。只接受 ref 可以形成更明确的契约;扩展输入形式应服务真实调用需求,并提供一致文档。
getter 每次返回新对象会有什么影响?
对象身份可能使下游观察到变化,若业务只依赖稳定字段,应选择该字段,避免无意义的包装对象更新。
普通值还能用于组合式函数吗?
当然可以,固定配置或只需初始化的参数适合普通值,只要不承诺它会跟随后续来源变化。针对“Vue 组合式函数 ref getter toValue 参数追踪”,还应保留最小复现、预期结果和失败路径,避免只凭一次现象下结论。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。