这一课非常关键,因为我们开始从"组件拆分"进入到"逻辑拆分"。
前几轮你已经看到:
- 页面头部可以拆组件
- 卡片可以拆组件
- 筛选栏可以拆组件
- 表格可以拆组件
- 弹窗也可以拆组件
但这时还会有一个问题:
就算模板都拆开了,页面级逻辑还是可能越来越多,怎么办?
这时候就该轮到组合式函数出场了。
先讲结论
你可以先记住一句非常重要的话:
组件主要负责界面结构,composable 主要负责可复用逻辑
也就是说:
.vue组件更偏向"长什么样、怎么渲染"useXxx.ts更偏向"状态怎么组织、逻辑怎么流动"
这就是 Vue 3 里组合式函数最有价值的地方。
这次我们做了什么
这一轮新增了:
src/composables/useTasksPage.ts
同时更新了:
src/views/TasksView.vuesrc/composables/__tests__/useTasksPage.spec.ts
这说明我们不是只写了一篇讲解文档,
而是真的把任务页页面级逻辑迁到了组合式函数中。
什么是组合式函数
你先不要把"组合式函数"想得太神秘。
它本质上就是:
把一组相关的响应式状态、计算属性和行为函数,打包到一个普通的 TypeScript 函数里
比如当前这个:
useTasksPage()
它把任务页相关的这些东西统一收口了:
- 任务列表
- 筛选条件
- 筛选结果
- 统计卡片
- 弹窗开关
- 创建任务逻辑
- 页面交互处理函数
这就是典型的组合式函数形态。
为什么这一步不是"组件拆分的重复"
因为组件拆分解决的是:
模板太大、区域太多
而组合式函数解决的是:
逻辑太多、状态太散
这两个不是一个问题。
举个直观的对比:
组件拆分前
- 模板很长
- 逻辑也在一个文件里
组件拆分后
- 模板被拆小了
- 但父页面里仍可能保留一大堆页面级状态和函数
引入 composable 后
- 页面模板清楚了
- 页面逻辑也开始集中抽离了
所以你要理解:
组件拆分是拆界面,composable 是拆逻辑
当前 useTasksPage.ts 里收了什么
这次迁进去的内容主要有这些:
1. 页面级状态
taskskeywordstatusFilterpriorityFilterdialogVisiblenextTaskId
这些都属于:
整个任务页都会用到的页面级状态
所以它们非常适合放进 composable,而不是塞在 TasksView.vue 里。
2. 页面级派生数据
filteredTaskstaskStatsdefaultAssignee
这些值都不是用户直接输入的原始数据,
而是根据已有状态算出来的。
这类东西和页面级状态关系非常紧密,
也很适合一起收进 composable。
3. 页面级行为函数
openCreateDialoghandleDialogVisibleChangecreateTaskhandleKeywordChangehandleStatusChangehandlePriorityChange
这些函数都在做同一件事:
驱动任务页这一个页面的整体行为
所以它们放到 useTasksPage() 里,就比散落在页面组件里更清晰。
为什么这些逻辑适合放到 composable
因为它们有一个共同点:
它们不依赖模板结构本身,而是依赖页面状态关系
比如:
- 任务怎么筛选
- 统计卡片怎么算
- 创建任务后列表怎么更新
- 弹窗开关怎么改
这些问题其实都和"HTML 长什么样"没直接关系。
它们描述的是页面行为规则。
而页面行为规则,正是组合式函数最擅长承载的东西。
现在的 TasksView.vue 为什么更清爽了
现在的 TasksView.vue 主要只做这些事情:
- 导入子组件
- 调用
useTasksPage() - 把 composable 返回的状态和方法接到模板上
- 在页面层补一个成功提示
这说明它已经非常接近一个理想状态:
页面组件只负责组装,不负责承载大量页面逻辑
这就是组合式函数真正带来的价值。
为什么 TasksView.vue 里还保留了一个 handleCreateTask
你会发现,现在页面里仍然保留了一个小函数:
ts
function handleCreateTask(taskDraft: TaskDraft) {
createTask(taskDraft)
ElMessage.success(...)
}
这一步很值得你学。
因为它体现了一个更细的分层思路:
useTasksPage()负责页面业务逻辑- 页面组件负责页面层反馈
也就是说:
- "把任务插入列表"是业务逻辑
- "弹出成功提示"更偏页面交互反馈
这不是绝对规则,但这是一个很合理的边界。
为什么 composable 里还能用 Store
useTasksPage.ts 里用了:
useUserStore()
这对初学者很重要,因为这说明:
composable 不是只能写纯函数,它完全可以组合已有的 Store、ref、computed 和函数逻辑
组合式函数本来就是"把多个能力再组合起来"的。
所以你不要把它理解成某种特殊限制很强的东西。
它本质上就是:
更高一层的逻辑复用容器
这次为什么顺手加了 composable 测试
这次新增了:
src/composables/__tests__/useTasksPage.spec.ts
这是很有教学价值的一步。
因为它让你看到:
逻辑迁进 composable 后,反而更容易单独测试
比如现在就能比较直接地测:
- 创建任务后列表是否更新
- 创建任务后弹窗是否关闭
- 状态筛选是否会影响过滤结果
这说明组合式函数不仅让代码更清晰,
也通常会让测试更容易写。
composable 和组件到底怎么分工
你现在可以这样记:
组件负责
- 模板结构
- 样式
- 局部界面交互
- 子组件组合
composable 负责
- 页面级状态
- 派生数据
- 页面级行为函数
- 跨多个组件共享的逻辑
一个最容易犯的错误
有些人学到 composable 之后,
会开始把所有东西都塞进 useXxx.ts。
这也不对。
你要避免两个极端:
极端 1
所有逻辑都堆在页面组件里。
极端 2
所有逻辑都强行塞进 composable,连页面层反馈都不留。
更合理的做法是:
按逻辑边界拆,不要为了用 composable 而用 composable
当前任务页为什么很适合开始引入 composable
因为现在它已经满足 3 个条件:
- 页面结构已经相对稳定
- 页面级逻辑已经明显聚成一团
- 这些逻辑并不依赖具体模板长相
这正是引入 composable 的好时机。
如果你在页面还没稳定时就急着抽 composable,
通常会抽得很乱。
所以顺序也很重要:
先把组件边界大致理顺,再抽页面逻辑
这次最值得你学的 4 个点
1. composable 是"逻辑容器",不是"组件替代品"
它不会替你渲染模板。
它只是帮你组织状态和行为。
2. 页面组件会越来越像"组装层"
这是好事,不是页面变弱了。
这说明职责更清楚了。
3. 页面级逻辑抽出后更容易测试
这对项目长期维护非常重要。
4. 组合式函数非常适合承载页面逻辑
尤其是:
- 多个状态之间有明显关系
- 有一组相关 computed
- 有一组相关行为函数
这几种情况一出现,就可以考虑 composable。
你现在应该能回答的 10 个问题
- 组件拆分和 composable 拆分分别解决什么问题?
useTasksPage()当前收了哪些页面级状态?- 为什么
filteredTasks和taskStats很适合放进 composable? - 为什么这些逻辑和模板结构没有直接关系?
- 为什么
TasksView.vue现在更清爽了? - 为什么页面层还保留了
handleCreateTask? - 为什么 composable 里也可以使用 Store?
- 为什么 composable 抽出来后更容易写单元测试?
- 什么时候适合开始引入 composable?
- 为什么不能把所有逻辑都无脑塞进 composable?
这节课的动手练习
练习 1
打开 useTasksPage.ts,把里面的内容按这 3 类重新标出来:
- 页面级状态
- 派生数据
- 页面级行为
目的:
训练你按逻辑角色读 composable。
练习 2
对照看:
找出:
- 哪些逻辑已经迁走
- 哪些逻辑还留在页面层
目的:
训练你分辨"业务逻辑"和"页面交互反馈"。
练习 3
打开 useTasksPage.spec.ts,回答:
- 它为什么不用渲染组件,也能测试任务页逻辑?
目的:
让你开始理解"逻辑抽离后测试更容易"这件事。
这节课的复习结论
把这一课压缩成 8 句话:
- 组件拆分解决界面复杂度,composable 拆分解决逻辑复杂度。
useTasksPage()把任务页的页面级状态、派生数据和行为函数统一收口了。- 页面逻辑不依赖模板时,通常很适合迁到 composable。
TasksView.vue现在更像页面组装层。- 页面层可以保留少量交互反馈逻辑,不必把一切都塞进 composable。
- composable 可以组合 Store、ref、computed 和函数逻辑。
- composable 抽出来后,通常更容易单独测试。
- 先理顺组件边界,再抽页面逻辑,是更稳的节奏。