第 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. 这次重构的关键不是搬模板,而是重画父子职责边界。

相关推荐
钛态2 小时前
前端WebSocket实时通信:别再用轮询了!
前端·vue·react·web
爱学习的程序媛2 小时前
浏览器内核揭秘:JavaScript 和 UI 的“主线程争夺战”
前端·性能优化·浏览器·web
你挚爱的强哥2 小时前
欺骗加载进度条,应用于无法监听接口数据传输进度的情况
前端·javascript·html
zhensherlock2 小时前
Protocol Launcher 系列:Mail Assistant 轻松发送 HTML 邮件
前端·javascript·typescript·node.js·html·github·js
恒本银河+2 小时前
基于MQTT+NFC标签项目开发教程
前端·javascript·nfc标签
吴声子夜歌2 小时前
ES6——异步操作和async函数详解
前端·ecmascript·es6
小小小米粒2 小时前
生命周期 = Vue 实例从创建 → 挂载 → 更新 → 销毁的全过程钩子函数computed = 基于依赖缓存的计算属性
前端·javascript·vue.js
IT_陈寒2 小时前
Vue的响应式更新把我坑惨了,原来是这个问题
前端·人工智能·后端
gyx_这个杀手不太冷静2 小时前
大人工智能时代下前端界面全新开发模式的思考(一)
前端·人工智能·ai编程