先记住这个答案
Vue 组合式函数通常以 use 开头,让调用者意识到它可能创建响应式状态、监听或生命周期关联;这个前缀本身没有特殊运行魔法。常见返回形式是普通对象包含多个 ref 和动作函数,调用者解构后仍能保留各个 ref 的响应能力。直接返回 reactive 对象也可以,但解构其中的原始字段可能变成当前值快照。应围绕状态所有权、解构方式和更新入口设计返回接口,而不是只统一函数名字。
- use 是约定,不会自动创建实例状态或执行清理
- 普通对象包 ref 便于解构后保留响应式引用
- 返回可写状态还是只读视图与动作,需要明确选择
前缀表达使用预期而不改变语义
看到 useSelection,调用者可以预期这是封装了选择状态和相关行为的函数,通常应查看它是否需要组件上下文、是否会注册监听。普通格式化函数即使也以 use 开头,不会因此突然具备响应式能力;反过来,不符合命名约定的函数也可能创建需要清理的副作用。
命名应说明封装的业务能力,例如分页、选择或连接状态,避免用过于宽泛的 useCommon 装入大量无关逻辑。一个组合式函数的输入、输出和清理责任越清楚,越容易被其他函数组合,而不需要读遍内部代码。
普通对象包 ref 为什么便于解构
下面每次调用都在函数内创建独立的选择状态,返回普通对象中的 selectedId 是 ref。调用者解构或重命名该字段,拿到的仍是同一个 ref;clear 则提供明确动作。函数没有依赖组件生命周期,因此这段纯响应式逻辑可以独立测试。
如果返回的是 reactive({ selectedId: null }),直接把 selectedId 解构出来通常只得到当时的原始值。可以保留整个对象使用,或按需求用 toRefs 提供字段引用,但不能假定普通 JavaScript 解构会自动维持对象属性的连接。
import { ref } from 'vue';
function useSelection() {
const selectedId = ref<string | null>(null);
function select(id: string) { selectedId.value = id; }
function clear() { selectedId.value = null; }
return { selectedId, select, clear };
}解构 selectedId 后调用 select,原 ref 的 value 会更新。两次 useSelection 调用各自创建状态,除非实现主动引用了外部共享容器。
返回结构还要表达更新权限
示例直接暴露可写 ref,调用者可以绕过 select 自行赋值,这适合简单状态接口。如果更新需要校验或联动多项状态,可以返回 readonly 视图并提供动作函数,让正常调用路径更清楚。只读设计也应说明实际约束范围,不能把它当作安全权限机制。
返回字段命名要避免和其他组合式函数频繁冲突,解构时可用局部重命名表达来源。增加功能时优先保持已有字段语义,不要把同名 data 从 ref 悄悄改成普通快照。测试除了当前值,还应检查解构后更新、多个实例隔离以及动作之间的状态关系。
容易答错的地方
- 用 use 命名就会自动清理副作用
- 清理来自正确的作用域、生命周期或显式停止逻辑,命名不会替实现完成这些工作。排查“Vue 组合式函数 use 命名与返回 refs 约定”时还要核对输入、版本和执行顺序,并用反例确认修复后的边界。
- reactive 返回对象随便解构都响应
- 解构原始字段可能丢失属性入口,应保留对象或提供合适的 ref。排查“Vue 组合式函数 use 命名与返回 refs 约定”时还要核对输入、版本和执行顺序,并用反例确认修复后的边界。
面试官还会怎么问?
一定要返回普通对象吗?
不是硬性限制。返回 reactive 对象适合整体使用的接口,但必须说明解构方式和字段引用的行为。
两个调用会天然共享状态吗?
不会由命名决定。状态在函数内还是模块外创建,才决定常见的共享范围。针对“Vue 组合式函数 use 命名与返回 refs 约定”,还应保留最小复现、预期结果和失败路径,避免只凭一次现象下结论。
什么时候需要只读返回值?
当更新需要遵守校验或多字段一致性时,只读视图配合动作可以减少误用;简单本地状态可以保留直接写入能力。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。