第 8 课:开始引入组合式函数

这一课非常关键,因为我们开始从"组件拆分"进入到"逻辑拆分"。

前几轮你已经看到:

  • 页面头部可以拆组件
  • 卡片可以拆组件
  • 筛选栏可以拆组件
  • 表格可以拆组件
  • 弹窗也可以拆组件

但这时还会有一个问题:

就算模板都拆开了,页面级逻辑还是可能越来越多,怎么办?

这时候就该轮到组合式函数出场了。


先讲结论

你可以先记住一句非常重要的话:

组件主要负责界面结构,composable 主要负责可复用逻辑

也就是说:

  • .vue 组件更偏向"长什么样、怎么渲染"
  • useXxx.ts 更偏向"状态怎么组织、逻辑怎么流动"

这就是 Vue 3 里组合式函数最有价值的地方。


这次我们做了什么

这一轮新增了:

  • src/composables/useTasksPage.ts

同时更新了:

  • src/views/TasksView.vue
  • src/composables/__tests__/useTasksPage.spec.ts

这说明我们不是只写了一篇讲解文档,

而是真的把任务页页面级逻辑迁到了组合式函数中。


什么是组合式函数

你先不要把"组合式函数"想得太神秘。

它本质上就是:

把一组相关的响应式状态、计算属性和行为函数,打包到一个普通的 TypeScript 函数里

比如当前这个:

  • useTasksPage()

它把任务页相关的这些东西统一收口了:

  • 任务列表
  • 筛选条件
  • 筛选结果
  • 统计卡片
  • 弹窗开关
  • 创建任务逻辑
  • 页面交互处理函数

这就是典型的组合式函数形态。


为什么这一步不是"组件拆分的重复"

因为组件拆分解决的是:

模板太大、区域太多

而组合式函数解决的是:

逻辑太多、状态太散

这两个不是一个问题。

举个直观的对比:

组件拆分前

  • 模板很长
  • 逻辑也在一个文件里

组件拆分后

  • 模板被拆小了
  • 但父页面里仍可能保留一大堆页面级状态和函数

引入 composable 后

  • 页面模板清楚了
  • 页面逻辑也开始集中抽离了

所以你要理解:

组件拆分是拆界面,composable 是拆逻辑


当前 useTasksPage.ts 里收了什么

这次迁进去的内容主要有这些:

1. 页面级状态

  • tasks
  • keyword
  • statusFilter
  • priorityFilter
  • dialogVisible
  • nextTaskId

这些都属于:

整个任务页都会用到的页面级状态

所以它们非常适合放进 composable,而不是塞在 TasksView.vue 里。


2. 页面级派生数据

  • filteredTasks
  • taskStats
  • defaultAssignee

这些值都不是用户直接输入的原始数据,

而是根据已有状态算出来的。

这类东西和页面级状态关系非常紧密,

也很适合一起收进 composable。


3. 页面级行为函数

  • openCreateDialog
  • handleDialogVisibleChange
  • createTask
  • handleKeywordChange
  • handleStatusChange
  • handlePriorityChange

这些函数都在做同一件事:

驱动任务页这一个页面的整体行为

所以它们放到 useTasksPage() 里,就比散落在页面组件里更清晰。


为什么这些逻辑适合放到 composable

因为它们有一个共同点:

它们不依赖模板结构本身,而是依赖页面状态关系

比如:

  • 任务怎么筛选
  • 统计卡片怎么算
  • 创建任务后列表怎么更新
  • 弹窗开关怎么改

这些问题其实都和"HTML 长什么样"没直接关系。

它们描述的是页面行为规则。

而页面行为规则,正是组合式函数最擅长承载的东西。


现在的 TasksView.vue 为什么更清爽了

现在的 TasksView.vue 主要只做这些事情:

  1. 导入子组件
  2. 调用 useTasksPage()
  3. 把 composable 返回的状态和方法接到模板上
  4. 在页面层补一个成功提示

这说明它已经非常接近一个理想状态:

页面组件只负责组装,不负责承载大量页面逻辑

这就是组合式函数真正带来的价值。


为什么 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 个条件:

  1. 页面结构已经相对稳定
  2. 页面级逻辑已经明显聚成一团
  3. 这些逻辑并不依赖具体模板长相

这正是引入 composable 的好时机。

如果你在页面还没稳定时就急着抽 composable,

通常会抽得很乱。

所以顺序也很重要:

先把组件边界大致理顺,再抽页面逻辑


这次最值得你学的 4 个点

1. composable 是"逻辑容器",不是"组件替代品"

它不会替你渲染模板。

它只是帮你组织状态和行为。


2. 页面组件会越来越像"组装层"

这是好事,不是页面变弱了。

这说明职责更清楚了。


3. 页面级逻辑抽出后更容易测试

这对项目长期维护非常重要。


4. 组合式函数非常适合承载页面逻辑

尤其是:

  • 多个状态之间有明显关系
  • 有一组相关 computed
  • 有一组相关行为函数

这几种情况一出现,就可以考虑 composable。


你现在应该能回答的 10 个问题

  1. 组件拆分和 composable 拆分分别解决什么问题?
  2. useTasksPage() 当前收了哪些页面级状态?
  3. 为什么 filteredTaskstaskStats 很适合放进 composable?
  4. 为什么这些逻辑和模板结构没有直接关系?
  5. 为什么 TasksView.vue 现在更清爽了?
  6. 为什么页面层还保留了 handleCreateTask
  7. 为什么 composable 里也可以使用 Store?
  8. 为什么 composable 抽出来后更容易写单元测试?
  9. 什么时候适合开始引入 composable?
  10. 为什么不能把所有逻辑都无脑塞进 composable?

这节课的动手练习

练习 1

打开 useTasksPage.ts,把里面的内容按这 3 类重新标出来:

  • 页面级状态
  • 派生数据
  • 页面级行为

目的:

训练你按逻辑角色读 composable。


练习 2

对照看:

找出:

  • 哪些逻辑已经迁走
  • 哪些逻辑还留在页面层

目的:

训练你分辨"业务逻辑"和"页面交互反馈"。


练习 3

打开 useTasksPage.spec.ts,回答:

  • 它为什么不用渲染组件,也能测试任务页逻辑?

目的:

让你开始理解"逻辑抽离后测试更容易"这件事。


这节课的复习结论

把这一课压缩成 8 句话:

  1. 组件拆分解决界面复杂度,composable 拆分解决逻辑复杂度。
  2. useTasksPage() 把任务页的页面级状态、派生数据和行为函数统一收口了。
  3. 页面逻辑不依赖模板时,通常很适合迁到 composable。
  4. TasksView.vue 现在更像页面组装层。
  5. 页面层可以保留少量交互反馈逻辑,不必把一切都塞进 composable。
  6. composable 可以组合 Store、ref、computed 和函数逻辑。
  7. composable 抽出来后,通常更容易单独测试。
  8. 先理顺组件边界,再抽页面逻辑,是更稳的节奏。
相关推荐
田八2 小时前
聊聊AI的发展史,AI的爆发并不是偶然
前端·人工智能·程序员
zhanghongbin012 小时前
AI 采集器:Claude Code、OpenAI、LiteLLM 监控
java·前端·人工智能
IT_陈寒2 小时前
Python的列表推导式里藏了个坑,差点让我加班到凌晨
前端·人工智能·后端
吴声子夜歌2 小时前
ES6——正则的扩展详解
前端·mysql·es6
格鸰爱童话2 小时前
向AI学习项目技能(五)
java·学习
技术人生黄勇2 小时前
拆解 Hermes Agent:开源 Agent 里唯一的闭环学习系统
学习
天若有情6732 小时前
【C++原创开源】formort.h:一行头文件,实现比JS模板字符串更爽的链式拼接+响应式变量
开发语言·javascript·c++·git·github·开源项目·模版字符串
天***88523 小时前
Edge 浏览器离线绿色增强版+官方安装包,支持win7等系统
前端·edge
漫游的渔夫3 小时前
别再直接 `json.loads` 了!AI 返回的 JSON 坑位指南
前端·人工智能