Vue3 组合式函数设计:复用逻辑之前先定义副作用
一、组合式函数不是万能抽屉
Vue3 Composition API 让逻辑复用更自然,但也容易被滥用。很多项目把请求、状态、事件监听、路由跳转、缓存和副作用都塞进一个useXxx。短期看复用方便,长期看很难测试和维护。
组合式函数设计的关键,是先定义它管理什么状态、产生什么副作用、由谁负责清理。复用逻辑之前,要先把边界讲清楚。
二、先区分纯状态和副作用
flowchart TD A[组合式函数] --> B[纯状态计算] A --> C[请求副作用] A --> D[DOM/事件副作用] A --> E[路由副作用]纯状态计算适合封装成小函数,副作用则要谨慎。比如usePagination可以只管理页码和大小;useUserList如果直接请求接口,就要处理 loading、error、取消请求和组件卸载。
一个组合式函数最好只承担一类副作用。否则调用者很难预测它会做什么。
三、接口要让调用者可控
export function useAsyncState<T>(loader: () => Promise<T>) { const data = ref<T | null>(null) const loading = ref(false) const error = ref<Error | null>(null) async function run() { loading.value = true try { data.value = await loader() } catch (e) { error.value = e as Error } finally { loading.value = false } } return { data, loading, error, run } }这个接口没有自动请求,调用者决定什么时候执行。自动执行不是不能做,但要通过参数明确。
composable_design: expose_run_function: true support_manual_trigger: true cleanup_side_effects: required avoid_hidden_router_change: true隐藏路由跳转、隐藏全局状态写入,是组合式函数最容易制造惊喜的地方。
四、测试要覆盖生命周期
组合式函数如果包含事件监听、定时器、请求取消,就要测试组件卸载后的清理行为。只测返回值是不够的。
还要控制命名。useUser太宽泛,调用者不知道它是读用户、改用户,还是监听用户变化。更具体的名字能减少误用。
组合式函数还要考虑并发调用。同一个页面多次调用useAsyncState时,状态是否共享,缓存是否复用,错误是否互相影响,都要明确。默认每次调用独立,通常比隐式共享更安全。
type UseRequestOptions = { immediate?: boolean cacheKey?: string abortOnUnmount?: boolean }如果支持缓存,就要让调用者显式传cacheKey。没有 key 的缓存很容易串数据,尤其是在用户切换、路由变化或参数变化时。
还要处理请求竞态。用户快速切换筛选条件时,旧请求可能晚于新请求返回。组合式函数可以用请求序号或 AbortController,确保旧结果不会覆盖新状态。
这些细节看起来琐碎,但它们决定组合式逻辑是否能在真实页面里长期稳定。
五、总结
Vue3 组合式函数设计要先区分纯状态和副作用,让调用者知道它会请求什么、监听什么、清理什么。
可复用不是把代码搬到useXxx里,而是让边界更清楚。