这一课是当前任务页重构里最重要的一轮。
因为前两轮更多是:
- 拆头部
- 拆卡片
- 拆筛选栏
- 拆表格
这些都属于"结构清晰,但状态并不复杂"的区域。
而这次我们要处理的是:
新增任务弹窗 + 表单状态
这就不是简单搬模板了。
先讲结论
这次重构真正解决的核心问题是:
表单状态到底应该留在父组件,还是可以迁移到弹窗组件内部?
在当前任务页这个场景下,答案是:
可以迁到弹窗组件内部
原因是:
- 这份表单状态只服务"新增任务弹窗"
- 它不会被页面其他区域直接使用
- 父组件真正关心的不是"表单每个字段怎么输入",而是"最终创建出来的新任务数据是什么"
所以这次重构的关键不是拆了一个 TaskCreateDialog.vue 文件,
而是:
我们把一整块局部状态和局部交互从父页面里搬走了
这次重构涉及哪些文件
新增文件
src/components/tasks/TaskCreateDialog.vue
更新文件
src/views/TasksView.vuesrc/types/task.tsdocs/README.md
为什么弹窗比表格更难拆
因为表格主要是:
接收数据 -> 显示数据
但弹窗是:
打开 -> 输入 -> 校验 -> 提交 -> 关闭 -> 重置
这是一整条局部业务链。
它涉及的东西远比表格多:
- 弹窗可见状态
- 表单字段状态
- 输入绑定
- 提交校验
- 提交事件
- 关闭行为
- 重置逻辑
所以你一定要有这个判断意识:
弹窗类组件通常比展示类组件更难拆,因为它不仅有模板,还有完整的交互状态流
这次为什么终于适合拆弹窗了
因为前面几轮重构已经做了铺垫。
现在 TasksView.vue 里剩下最重的一块,基本就是弹窗表单。
而且这时页面整体结构已经足够清楚:
- 头部独立了
- 卡片独立了
- 筛选栏独立了
- 表格独立了
这意味着父组件现在已经很接近一个标准页面容器。
此时再处理弹窗,风险就比一开始直接动它小得多。
这就是为什么重构要讲顺序。
新组件 TaskCreateDialog.vue 负责什么
它现在负责:
- 弹窗显示
- 表单状态
- 输入绑定
- 本地校验
- 提交事件抛出
- 关闭后的表单重置
你会发现,这已经不是简单的展示组件了,
而是一个非常典型的:
局部业务组件
这类组件在中后台项目里非常常见。
这次新增的 TaskDraft 类型有什么意义
在 task.ts 里,这次新增了:
TaskDraft
它的意义是:
让弹窗内部表单状态和父组件接收到的提交数据,共享同一套结构定义
这很好,因为:
- 子组件内部表单不会乱写字段
- 父组件接收数据时也更明确
- 以后如果字段增加,类型会提醒你所有关联代码一起改
你可以把它理解成:
这是新增任务这件事的数据契约
现在弹窗组件的输入是什么
当前 TaskCreateDialog.vue 接收这些输入:
visibledefaultAssigneedefaultDueDatestatusOptionspriorityOptions
这些输入说明了一个很重要的边界:
- 弹窗不是完全独立的
- 它仍然需要父组件提供上下文
比如:
- 当前默认负责人是谁
- 当前有哪些可选状态
- 当前有哪些可选优先级
- 现在弹窗到底该不该显示
这就是典型的"局部组件依赖父页面上下文"的情况。
现在弹窗组件的输出是什么
当前它有两个核心输出:
update:visiblesubmit
这两个事件非常关键。
update:visible
用来让父组件知道:
- 弹窗应该关闭了
- 或者显示状态发生变化了
submit
用来把整理好的任务草稿抛给父组件。
这意味着:
- 子组件负责收集和校验输入
- 父组件负责决定如何把数据写入任务列表
这是一种很好的分工。
为什么表单状态这次迁到了子组件内部
这一步是本轮最值得你学的设计判断。
之前表单状态在父组件里:
taskFormresetTaskForm()
现在这些内容已经迁到 TaskCreateDialog.vue 里了。
为什么这么做合理?
因为这份状态:
- 只服务新增任务弹窗
- 不被表格、筛选栏、统计卡片直接依赖
- 父组件其实只需要最终提交结果,不需要盯着每个输入过程
这说明它是典型的:
局部状态
而局部状态通常更适合放在局部组件里。
这一步和前面几轮最大的不同
前面拆头部、卡片、表格时,
更多是在做:
模板下沉
而这次拆弹窗,除了模板,还做了:
- 状态下沉
- 表单逻辑下沉
- 重置逻辑下沉
也就是说,这次不是"把代码放到别的文件",
而是:
重新定义了父子组件之间的责任划分
这才是更深层的组件化。
父组件这次又轻了什么
现在 TasksView.vue 已经不再负责:
taskForm- 表单输入默认值对象
- 表单字段双向绑定
- 弹窗模板结构
- 表单重置函数
- 标题必填校验
它只保留:
- 页面级弹窗开关
dialogVisible - 主任务列表
tasks - 新任务入库逻辑
createTask(taskDraft)
这说明父组件越来越接近一个真正清晰的页面容器了。
这次 createTask 为什么变了
以前是:
ts
function createTask() {
...
}
它直接读取父组件内部的 taskForm。
现在变成:
ts
function createTask(taskDraft: TaskDraft) {
...
}
这表示:
- 父组件不再依赖弹窗内部的输入细节
- 父组件只接收一份已经整理好的结果
这是非常重要的一步。
因为它让父组件从:
知道表单内部一切细节
变成:
只接收子组件产出的业务结果
这就是组件边界真正变清晰的标志。
为什么校验放在子组件里是合理的
当前标题必填校验已经在 TaskCreateDialog.vue 里完成。
这很合理,因为:
- 标题输入本来就在这个组件里
- 用户交互也发生在这个组件里
- 这个校验属于表单局部校验
所以它更适合就近处理。
如果以后你遇到的是:
- 跨组件联动校验
- 依赖页面其他状态的复杂校验
那就不一定都适合放在子组件内部了。
所以你要学会区分:
- 局部校验
- 页面级校验
为什么关闭重置也放进子组件里
当前重置逻辑也迁到了 TaskCreateDialog.vue 里。
这是很自然的,因为:
- 表单在这个组件里
- 重置对象也在这个组件里
所以"关闭后重置表单"这件事,本来就属于它的内部清理职责。
这一步也很值得你记住:
谁拥有局部状态,谁通常就应该负责局部状态的清理
现在任务页已经接近什么状态
重构到这一步,TasksView.vue 基本已经变成了一个很像样的组合页:
vue
<TaskPageHeader />
<TaskStatsCards />
<TaskFilterBar />
<TaskTable />
<TaskCreateDialog />
而父组件真正保留的内容主要是:
- 页面级状态
- 列表和筛选逻辑
- 新任务入库逻辑
- 子组件组合关系
这就是一个标准的中后台业务页正在成形的样子。
这次重构最值得你学的 4 个点
1. 局部状态可以下沉
不是所有状态都必须留在父组件。
只服务局部组件的状态,通常可以考虑下沉。
2. 父组件应该尽量只关心业务结果
现在父组件不再关心"表单里每一项怎么输入",
而只关心"最终新增的任务数据是什么"。
这就是更成熟的数据边界。
3. 表单组件通常需要同时管理模板、状态和清理逻辑
这也是为什么它比展示组件更复杂。
4. 组件化不是把所有逻辑都丢给子组件
父组件仍然保留了最关键的页面级业务:
- 任务列表
- 统计
- 筛选
- 入库
所以正确做法不是"全扔子组件",
而是按边界分配职责。
你现在应该能回答的 10 个问题
- 为什么新增任务弹窗比表格更难拆?
- 为什么这轮终于适合处理弹窗了?
TaskDraft这个类型解决了什么问题?- 当前弹窗组件的输入是什么?
- 当前弹窗组件的输出是什么?
- 为什么表单状态适合迁到弹窗组件内部?
- 为什么
createTask现在改成接收参数了? - 为什么局部校验适合放在子组件里?
- 为什么关闭重置也适合放在子组件里?
- 现在的
TasksView.vue为什么已经很像标准组合页了?
这节课的动手练习
练习 1
打开 TaskCreateDialog.vue,回答:
- 它负责什么
- 它依赖什么输入
- 它向父组件输出什么
目的:
训练你看懂"局部业务组件"的完整边界。
练习 2
对照看:
找出:
- 哪些逻辑还在父组件里
- 哪些逻辑已经迁到子组件里
目的:
训练你分辨页面级状态和局部状态。
练习 3
自己思考一个问题:
如果以后新增任务要接后端接口,
这个接口请求更适合放在:
TaskCreateDialog.vue
还是TasksView.vue
并给出理由。
目的:
开始思考"局部交互"和"页面级业务"的边界差别。
这节课的复习结论
把这次第三轮重构压缩成 8 句话:
- 弹窗表单比表格更难拆,因为它有完整的局部状态流。
TaskCreateDialog.vue不是纯展示组件,而是局部业务组件。TaskDraft让弹窗内部和父组件之间共享同一份数据契约。- 只服务弹窗的表单状态,适合迁到弹窗组件内部。
- 父组件现在只关心最终提交结果,而不再关心每个输入细节。
- 局部校验和局部重置逻辑,通常适合放在子组件内部。
TasksView.vue现在已经很接近标准的组合页结构。- 这次重构的关键不是搬模板,而是重画父子职责边界。