Vue 组合式函数边界:复用逻辑,不要顺手复用混乱
一、Composable 不是万能收纳盒
Vue3 组合式 API 让逻辑复用变得很方便,useXxx函数也越来越多。但很多项目把请求、状态、路由、弹窗、副作用全塞进一个 composable,最后复用是复用了,混乱也一起复用了。
组合式函数要有清楚边界:它负责什么,不负责什么,生命周期由谁管理。
二、先定义职责
flowchart TD A[Composable] --> B[状态封装] A --> C[副作用] A --> D[事件暴露] A --> E[清理机制]一个 composable 可以封装状态和行为,但不要偷偷操作太多外部环境。比如useUserList如果同时改路由、弹 toast、写全局 store,就很难复用。
export function useUserQuery(params: Ref<QueryParams>) { const loading = ref(false) const data = ref<User[]>([]) return { loading, data, refresh } }这种函数只负责查询状态,调用方决定怎么展示错误和跳转。
三、副作用要能退出
export function useWindowSize() { const width = ref(window.innerWidth) const onResize = () => (width.value = window.innerWidth) onMounted(() => window.addEventListener('resize', onResize)) onUnmounted(() => window.removeEventListener('resize', onResize)) return { width } }监听事件、定时器、订阅、请求轮询都属于副作用。composable 创建了副作用,就要负责清理。否则页面切换几次后,旧监听还在,性能和行为都会出问题。
如果 composable 可能在组件外使用,就不能依赖组件生命周期,要提供显式 stop 方法。
四、返回值要稳定
返回一堆内部细节,会让调用方和实现强耦合。返回值应该表达使用场景,而不是暴露所有状态。
type UseSearchResult = { keyword: Ref<string> results: Ref<Item[]> search: () => Promise<void> reset: () => void }命名也要一致。refresh、reload、fetchData混用,会让团队不知道哪个函数做什么。规范不是为了好看,是为了减少误用。
组合式函数之间也要避免互相套娃。A 调 B,B 调 C,C 又改全局状态,最后排查一个响应式更新要翻半天。可复用逻辑要保持薄,复杂流程更适合放到业务 service。
最后,给 composable 写测试。至少覆盖初始状态、成功路径、失败路径和清理逻辑。没有测试的复用,改一次影响十个页面,谁都不敢动。
还要区分业务 composable 和基础 composable。useWindowSize、useDebounce是基础能力,应该稳定、无业务依赖;useOrderSearch、useCouponPanel是业务能力,可以依赖接口和权限。两类函数混在一起,会让基础层被业务污染。
composable_layers: base: allow_business_api: false domain: allow_business_api: true require_owner: true目录结构也要表达边界。基础 composable 放公共包,业务 composable 放业务模块内,避免为了“复用”把所有逻辑扔进一个hooks目录。
五、总结
Vue 组合式函数要明确职责、控制副作用、提供清理机制、保持返回值稳定,并用测试保护复用边界。
复用逻辑很好,但不要顺手把混乱也复用出去。