第 7 课:第三轮真实重构,拆出新增任务弹窗

这一课是当前任务页重构里最重要的一轮。

因为前两轮更多是:

  • 拆头部
  • 拆卡片
  • 拆筛选栏
  • 拆表格

这些都属于"结构清晰,但状态并不复杂"的区域。

而这次我们要处理的是:

新增任务弹窗 + 表单状态

这就不是简单搬模板了。


先讲结论

这次重构真正解决的核心问题是:

表单状态到底应该留在父组件,还是可以迁移到弹窗组件内部?

在当前任务页这个场景下,答案是:

可以迁到弹窗组件内部

原因是:

  • 这份表单状态只服务"新增任务弹窗"
  • 它不会被页面其他区域直接使用
  • 父组件真正关心的不是"表单每个字段怎么输入",而是"最终创建出来的新任务数据是什么"

所以这次重构的关键不是拆了一个 TaskCreateDialog.vue 文件,

而是:

我们把一整块局部状态和局部交互从父页面里搬走了


这次重构涉及哪些文件

新增文件

  • src/components/tasks/TaskCreateDialog.vue

更新文件

  • src/views/TasksView.vue
  • src/types/task.ts
  • docs/README.md

为什么弹窗比表格更难拆

因为表格主要是:

接收数据 -> 显示数据

但弹窗是:

打开 -> 输入 -> 校验 -> 提交 -> 关闭 -> 重置

这是一整条局部业务链。

它涉及的东西远比表格多:

  • 弹窗可见状态
  • 表单字段状态
  • 输入绑定
  • 提交校验
  • 提交事件
  • 关闭行为
  • 重置逻辑

所以你一定要有这个判断意识:

弹窗类组件通常比展示类组件更难拆,因为它不仅有模板,还有完整的交互状态流


这次为什么终于适合拆弹窗了

因为前面几轮重构已经做了铺垫。

现在 TasksView.vue 里剩下最重的一块,基本就是弹窗表单。

而且这时页面整体结构已经足够清楚:

  • 头部独立了
  • 卡片独立了
  • 筛选栏独立了
  • 表格独立了

这意味着父组件现在已经很接近一个标准页面容器。

此时再处理弹窗,风险就比一开始直接动它小得多。

这就是为什么重构要讲顺序。


新组件 TaskCreateDialog.vue 负责什么

它现在负责:

  1. 弹窗显示
  2. 表单状态
  3. 输入绑定
  4. 本地校验
  5. 提交事件抛出
  6. 关闭后的表单重置

你会发现,这已经不是简单的展示组件了,

而是一个非常典型的:

局部业务组件

这类组件在中后台项目里非常常见。


这次新增的 TaskDraft 类型有什么意义

task.ts 里,这次新增了:

  • TaskDraft

它的意义是:

让弹窗内部表单状态和父组件接收到的提交数据,共享同一套结构定义

这很好,因为:

  • 子组件内部表单不会乱写字段
  • 父组件接收数据时也更明确
  • 以后如果字段增加,类型会提醒你所有关联代码一起改

你可以把它理解成:

这是新增任务这件事的数据契约


现在弹窗组件的输入是什么

当前 TaskCreateDialog.vue 接收这些输入:

  • visible
  • defaultAssignee
  • defaultDueDate
  • statusOptions
  • priorityOptions

这些输入说明了一个很重要的边界:

  • 弹窗不是完全独立的
  • 它仍然需要父组件提供上下文

比如:

  • 当前默认负责人是谁
  • 当前有哪些可选状态
  • 当前有哪些可选优先级
  • 现在弹窗到底该不该显示

这就是典型的"局部组件依赖父页面上下文"的情况。


现在弹窗组件的输出是什么

当前它有两个核心输出:

  • update:visible
  • submit

这两个事件非常关键。

update:visible

用来让父组件知道:

  • 弹窗应该关闭了
  • 或者显示状态发生变化了

submit

用来把整理好的任务草稿抛给父组件。

这意味着:

  • 子组件负责收集和校验输入
  • 父组件负责决定如何把数据写入任务列表

这是一种很好的分工。


为什么表单状态这次迁到了子组件内部

这一步是本轮最值得你学的设计判断。

之前表单状态在父组件里:

  • taskForm
  • resetTaskForm()

现在这些内容已经迁到 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 个问题

  1. 为什么新增任务弹窗比表格更难拆?
  2. 为什么这轮终于适合处理弹窗了?
  3. TaskDraft 这个类型解决了什么问题?
  4. 当前弹窗组件的输入是什么?
  5. 当前弹窗组件的输出是什么?
  6. 为什么表单状态适合迁到弹窗组件内部?
  7. 为什么 createTask 现在改成接收参数了?
  8. 为什么局部校验适合放在子组件里?
  9. 为什么关闭重置也适合放在子组件里?
  10. 现在的 TasksView.vue 为什么已经很像标准组合页了?

这节课的动手练习

练习 1

打开 TaskCreateDialog.vue,回答:

  • 它负责什么
  • 它依赖什么输入
  • 它向父组件输出什么

目的:

训练你看懂"局部业务组件"的完整边界。


练习 2

对照看:

找出:

  • 哪些逻辑还在父组件里
  • 哪些逻辑已经迁到子组件里

目的:

训练你分辨页面级状态和局部状态。


练习 3

自己思考一个问题:

如果以后新增任务要接后端接口,

这个接口请求更适合放在:

  • TaskCreateDialog.vue
    还是
  • TasksView.vue

并给出理由。

目的:

开始思考"局部交互"和"页面级业务"的边界差别。


这节课的复习结论

把这次第三轮重构压缩成 8 句话:

  1. 弹窗表单比表格更难拆,因为它有完整的局部状态流。
  2. TaskCreateDialog.vue 不是纯展示组件,而是局部业务组件。
  3. TaskDraft 让弹窗内部和父组件之间共享同一份数据契约。
  4. 只服务弹窗的表单状态,适合迁到弹窗组件内部。
  5. 父组件现在只关心最终提交结果,而不再关心每个输入细节。
  6. 局部校验和局部重置逻辑,通常适合放在子组件内部。
  7. TasksView.vue 现在已经很接近标准的组合页结构。
  8. 这次重构的关键不是搬模板,而是重画父子职责边界。

相关推荐
梨子同志8 分钟前
qiankun
前端
梨子同志17 分钟前
Nginx
前端
梨子同志18 分钟前
NestJS
前端
梨子同志33 分钟前
Next.js
前端
谭光志43 分钟前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
Hilaku1 小时前
前端架构师的克制:什么时候该坚决对新技术说不?
前端·javascript·程序员
陈随易1 小时前
前端项目部署只要30秒
前端·后端·程序员
本末倒置1831 小时前
从 Vue CLI 迁移到 Rsbuild 后,JSX 突然全挂了?一次排查记录
前端
古夕1 小时前
多应用同域部署下 Favicon 异常问题排查与治理
前端·javascript
YIAN1 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp