这一课和前几课不一样。
前几课更多是在讲"应该怎么理解"。
这一课开始,我们正式进入:
真的动手把一个页面拆成组件
这次我已经把任务页完成了第一批重构,拆出了 3 个组件:
TaskPageHeader.vueTaskStatsCards.vueTaskFilterBar.vue
你现在要学的重点不是"拆出了几个文件",
而是:
为什么先拆这 3 个,而不是别的部分?
这次重构涉及哪些文件
新增文件
src/components/tasks/TaskPageHeader.vuesrc/components/tasks/TaskStatsCards.vuesrc/components/tasks/TaskFilterBar.vue
更新文件
src/views/TasksView.vuesrc/types/task.ts
先看重构前的 TasksView.vue 有什么问题
重构前,这个页面虽然能跑,也不算写坏了,但已经出现了典型的"大页面信号":
- 头部模板和业务逻辑混在一起
- 统计卡片模板和表格模板放在同一个文件里
- 筛选栏的结构和筛选状态逻辑也都堆在一起
- 读代码时很难只聚焦某一块职责
也就是说:
它不是不能维护,
但已经开始"读起来越来越费劲"。
这就是第一次重构最合适的时机。
为什么这次先拆 3 个组件
因为它们满足两个条件:
- 职责边界清楚
- 拆出去后不会马上把状态流弄复杂
这很重要。
初学者第一次拆组件,
不要一上来就拆最复杂的弹窗表单,
而要先拆"清晰、稳定、低风险"的区域。
所以这次我优先拆了:
- 页面头部
- 统计卡片
- 筛选栏
这是一种非常稳的重构顺序。
第一个组件:TaskPageHeader.vue
它负责的事情非常单纯:
- 显示标题
- 显示描述
- 显示"新增任务"按钮
它对父组件的唯一输出就是:
create事件
所以你可以把它理解成:
一个很轻的交互型组件
它不是页面控制中心,
它只是:
用户点击按钮 -> 通知父组件
这类组件特别适合作为第一次拆分练手对象。
第二个组件:TaskStatsCards.vue
这个组件更简单,它属于典型的:
纯展示组件
它做的事情是:
- 接收
stats - 循环渲染卡片
它几乎不负责业务逻辑,也不管理状态。
所以它是初学组件化时最值得优先拆的一类组件。
你以后如果看到这种区域:
- 数据已经算好了
- 组件只负责显示
那基本都很适合先抽出来。
第三个组件:TaskFilterBar.vue
这个组件比前两个更有教学价值。
因为它已经不是纯展示了,
而是一个典型的:
有输入、有交互、有输出的组件
它的输入是:
keywordstatusFilterpriorityFilterstatusOptionspriorityOptions
它的输出是:
keyword-changestatus-changepriority-change
这说明它已经具备了完整的组件边界:
- 输入靠 props
- 输出靠 emits
这就是你以后写业务组件时最常见的模式。
为什么筛选栏没有自己保存筛选状态
这是这次重构里最关键的设计点之一。
我们没有把:
keywordstatusFilterpriorityFilter
都放进 TaskFilterBar.vue 自己内部去管理。
而是让它继续留在 TasksView.vue。
原因是:
这些筛选条件会直接影响页面级的 filteredTasks 和 taskStats
所以它们更适合保留在父组件里。
也就是说:
- 子组件只负责"收集用户输入"
- 父组件负责"拿这些输入去驱动页面业务"
这就是一个非常重要的组件化思维。
这次重构后,父组件发生了什么变化
重构后的 TasksView.vue 更像一个"页面编排层"。
它现在更专注于这些内容:
- 持有主数据
tasks - 持有筛选状态
- 持有弹窗状态
- 计算
filteredTasks - 计算
taskStats - 处理新增任务逻辑
- 把状态和事件分发给各个子组件
这说明父组件的职责更清楚了:
负责页面级状态和业务组织
而不是既写业务,又写所有细节模板。
这就是"组合页"的雏形
现在的 TasksView.vue 已经开始具备一种很好的结构:
vue
<TaskPageHeader />
<TaskStatsCards />
<TaskFilterBar />
<el-table ... />
<el-dialog ... />
这意味着页面开始从:
一大块复杂代码
变成:
多个职责明确的区块组合
这就是你以后在中大型 Vue 项目里最常见的页面组织方式。
为什么这次没有马上拆表格和弹窗
这是一个非常值得你学的决定。
很多初学者重构时容易冲动:
既然都拆了,那就一次性全拆完
但这通常不是最稳的方式。
这次没有马上拆表格和弹窗,是因为:
1. 表格里还有展示规则函数依赖
例如:
getStatusTypegetPriorityType
这些逻辑和页面当前状态还贴得比较近。
2. 弹窗表单是当前页面最复杂的一块
它涉及:
dialogVisibletaskFormresetTaskFormcreateTask- 表单默认值
- 表单校验
这块如果第一次重构就直接拆,
对于初学者来说,状态流会一下子变复杂。
所以更稳的策略是:
先拆简单区块,再拆复杂区块
这也是我为什么把它留到下一轮。
这次重构额外补了什么
除了拆组件,这次我还补了通用类型:
TaskStatusFilterTaskPriorityFilterTaskStatCard
这一步的意义是:
让父组件和子组件共享同一套数据定义
如果没有这些类型,
每个组件都自己写一遍,后面很容易不统一。
所以你也要慢慢形成习惯:
组件一多,就要开始考虑共享类型
这次重构最值得你学的 3 个点
1. 不是所有组件都一样
当前 3 个组件就已经体现了 3 种典型角色:
TaskPageHeader.vue
轻交互组件TaskStatsCards.vue
纯展示组件TaskFilterBar.vue
输入输出明确的交互组件
这说明:
组件化不是一种固定模板,而是多种职责形态
2. 组件拆分后,状态边界更重要
你不能只想着"模板拆出去了",
还要问自己:
- 状态应该放哪儿?
- 谁负责计算结果?
- 谁负责抛出事件?
这才是组件化真正难也真正有价值的部分。
3. 重构要循序渐进
这次只拆了第一批组件。
这不是没拆完,而是故意控制节奏。
因为好的重构通常是:
- 每次只改一层复杂度
- 每次都保证页面还能稳定运行
- 每次都让结构比之前更清晰
这比一次性大改更稳、更适合学习。
你现在应该能回答的 10 个问题
- 为什么这次优先拆的是头部、统计卡片、筛选栏?
- 为什么统计卡片是典型的纯展示组件?
- 为什么筛选栏已经具备组件化边界?
- 为什么筛选状态还应该保留在父组件里?
- 拆组件后父组件最主要的职责变成了什么?
- 为什么这次没有立刻拆表格?
- 为什么这次没有立刻拆弹窗表单?
- 为什么共享类型在组件变多后会越来越重要?
props + emits解决的到底是什么问题?- 为什么"渐进式重构"比"一次性重写"更适合实战?
这节课的动手练习
练习 1
先打开这三个组件文件:
src/components/tasks/TaskPageHeader.vuesrc/components/tasks/TaskStatsCards.vuesrc/components/tasks/TaskFilterBar.vue
然后分别回答:
- 它负责什么
- 它接收什么
- 它输出什么
目的:
训练你按组件边界来读代码。
练习 2
打开 src/views/TasksView.vue,找到这 3 类内容:
- 页面级状态
- 传给子组件的 props
- 从子组件接回来的事件
目的:
训练你看懂父子组件的数据流。
练习 3
试着自己判断:
如果下一步要继续拆,应该先拆:
- 表格
还是 - 弹窗
并说明理由。
目的:
练习"重构优先级判断",而不是只会照着拆。
这节课的复习结论
把这次真实重构压缩成 8 句话:
- 第一次重构要先拆边界清晰、风险低的区域。
- 头部、统计卡片、筛选栏是当前任务页最适合的第一批组件。
TaskStatsCards.vue是纯展示组件。TaskPageHeader.vue是轻交互组件。TaskFilterBar.vue是有明确输入输出的交互组件。- 页面级状态仍然保留在父组件里更稳。
- 父组件现在更像页面业务编排中心。
- 渐进式重构比一次性大拆更适合学习和实战。