组合式函数抽离关键在于识别有状态、有副作用、可复用的业务单元并自然封装。需优先抽取重复逻辑、响应式状态、生命周期绑定、副作用及明确状态流转;命名规范、职责单一、接口精简、资源清理到位;按业务域组织,支持ts,外部传参控制初始行为。

把 setup 里的复杂逻辑抽离为组合式函数,关键不是“搬代码”,而是识别出有状态、有副作用、可复用的业务单元,再用函数把它自然封装起来。
一、先判断哪些逻辑值得抽
不是所有代码都要抽。优先考虑以下情况:
- 同一段逻辑在两个及以上组件中重复出现(比如列表请求+分页+搜索)
- 涉及响应式状态(如
ref管理的loading、list、error) - 绑定生命周期(例如
onMounted自动加载、onUnmounted清理定时器或事件监听) - 包含副作用(调用 API、读写 localStorage、监听页面 resize 或滚动)
- 有明确的状态流转(防抖输入、倒计时、错误重试、权限校验流程)
二、怎么写一个干净可用的组合式函数
以封装“带分页的列表请求”为例(useListRequest.js):
- 文件名和函数名都以
use开头,放在src/composables/下 - 接收必要参数(如
url、默认params),不硬编码业务路径或 axios 实例 - 内部用
ref和computed管理响应式数据,用watch或onMounted触发逻辑 - 暴露最小必要接口:比如
list、loading、fetch、refresh - 在
onUnmounted或 watch 变化时主动清理资源(如 abort controller、clearTimeout)
三、在组件中怎么用才不乱
<script setup></script> 中直接导入并解构,不包装、不合并、不二次加工:
- 多个函数可同时调用,互不影响:
const { list, loading } = useListRequest('/api/users'),const { valid, validate } = useFormValidation() - 避免把不同职责的状态塞进同一个函数(比如不要把“用户信息”和“图表尺寸”合在
useCommon里) - 需要跨函数通信时,用参数传回调(如
onSuccess: () => refreshChart()),而不是共享 ref 或滥用 provide/inject - 返回值结构尽量稳定,比如统一含
loading、error、refresh字段,方便模板统一处理
四、组织与维护要点
拆分不是目的,清晰和可持续才是:
- 每个组合式函数对应一个独立 .js 文件,导出默认函数,命名即语义(
useSearch、useDarkMode) - 按业务域组织,而非技术类型——比如
useOrderList比useApi更易理解、更易定位 - 支持 TypeScript:参数、返回值、泛型(如
useFetch<user>()</user>)都标注清楚 - 初始状态从外部传入(
initialPage = 1),不在函数内做 localStorage 判断或默认 API 请求










