Skip to content

086. 表格主体模块拆分与图片列扩展

学习目标

这一节开始拆分通用表格主体文件,并演示自定义图片列扩展。

学完后,你应该能理解:

  • 为什么 createAutoTable 主体文件需要拆分;
  • 表格能力为什么也要设计成模块;
  • 模块安装顺序为什么重要;
  • 如何通过类型扩展给表格对象追加能力;
  • 如何新增一个自定义图片列;
  • 列扩展和表格模块扩展有什么不同。

为什么要拆主体文件

通用表格主体函数已经有几百行。

里面混在一起的内容包括:

  • 配置初始化;
  • 状态管理;
  • 表格方法;
  • 事件处理;
  • 生命周期逻辑;
  • 渲染逻辑。

功能继续增加后,这个文件会越来越难读。

所以要把它拆成多个模块。

拆分不是为了好看。

它是后面做扩展机制的前提。

表格能力也要可扩展

前面已经实现了列可扩展。

不同项目可以新增自己的列类型。

但通用表格不只需要扩展列。

还需要扩展功能模块。

例如:

  • 查询表单;
  • 工具栏按钮;
  • 表格顶部内容;
  • 表格底部内容;
  • 批量操作;
  • 自定义状态;
  • 自定义行为。

所以表格主体也要拆成模块。

先拆分,再讲扩展

这一节先做拆分。

模块扩展机制会在后面的内容里继续展开。

现在要先把主体函数里的几类职责拆出去。

可以先按注释区域拆:

  • config;
  • state;
  • handler;
  • lifecycle;
  • render。

每一块都可以理解成一个模块。

配置模块

配置模块负责处理表格配置。

它会维护:

  • 默认配置;
  • 用户传入配置;
  • 运行时配置;
  • 更新配置的方法。

配置模块安装到表格对象后,表格对象就能拿到配置相关能力。

例如:

ts
autoTable.config
autoTable.runningConfig
autoTable.setConfig

这里的具体命名以代码为准,关键是职责边界。

配置相关逻辑不再散落在主体函数里。

表格对象类型

原来可以通过函数返回值推导表格对象类型。

但这种方式不适合模块扩展。

因为后续模块会不断往表格对象上追加属性和方法。

所以需要定义一个可扩展的接口。

可以理解成:

ts
interface IAutoTable {}

配置模块、状态模块、渲染模块都可以继续扩展这个接口。

这样模块安装后,TypeScript 也知道表格对象新增了什么能力。

状态模块

状态模块负责集中管理表格状态。

包括:

  • 数据;
  • 加载状态;
  • 分页;
  • 编辑行 ID 映射;
  • 新增行 ID 映射;
  • Form 实例管理;
  • 更新这些状态的方法。

课程里把状态分成两类结果:

  • state:状态值;
  • methods:修改状态的方法。

模块安装后,它们会被挂到表格对象上。

这样其他模块可以通过表格对象访问状态和方法。

Dispatch 类型

React 的 set 函数类型不只是“传一个值”。

它也可以接收一个函数。

例如:

ts
setData(nextData)
setData(prev => nextData)

所以手写类型会比较麻烦。

可以使用 React 提供的 DispatchSetStateAction 类型。

对 JS 开发经验来说,先理解用途即可:它表示一个 React 状态更新函数。

模块安装

每个模块都是一个函数。

它接收表格对象和必要参数。

执行后,把自己的能力挂到表格对象上。

例如:

ts
installConfigModule(autoTable)
installStateModule(autoTable)
installRenderModule(autoTable)

安装后,后续模块才能读取前面模块提供的内容。

这就引出一个重点:模块安装有顺序。

模块顺序

如果渲染模块要读取状态模块的数据,那么状态模块必须先安装。

否则渲染模块执行时,表格对象上还没有对应属性。

这和后端依赖注入、插件注册有点像。

你不能在服务还没有注册之前就使用它。

在通用表格里,模块顺序会影响:

  • 类型是否可用;
  • 状态是否存在;
  • 方法是否能调用;
  • 渲染逻辑是否能拿到完整上下文。

渲染模块

渲染模块负责返回真正的表格内容。

主体函数拆分后,主体文件可以变得很短。

它主要负责:

  1. 创建表格对象;
  2. 安装各个模块;
  3. 返回渲染函数或渲染结果。

渲染细节放到独立文件。

这样主体函数就不再承载所有 UI 逻辑。

临时保留 handler

事件处理模块在这一节还没有完全消除。

部分 handler 暂时保留。

原因是后续会用事件钩子的方式,让模块之间通信。

现在先完成主体拆分。

后面再把事件处理改造成更完整的模块机制。

这也是拆复杂代码时常见的节奏:

先拆出大块边界,再逐步优化模块之间的通信方式。

拆分后的主体函数

拆完后,主体文件从几百行变成几十行。

它不再直接维护所有状态和渲染细节。

它更像一个组装器。

组装器负责把配置模块、状态模块、渲染模块等装到同一个表格对象上。

这为后面“添加、删除、覆盖模块”留下空间。

表格模块能扩展什么

表格模块的目标是让调用方可以往表格不同位置加能力。

例如:

  • 在表格顶部加查询表单;
  • 在查询区右侧加按钮;
  • 在表格工具栏加按钮;
  • 在表格底部加统计信息;
  • 添加自定义状态;
  • 添加自定义事件。

这类能力不能只靠列扩展实现。

列扩展解决的是字段怎么显示、怎么编辑。

模块扩展解决的是表格整体功能怎么组合。

自定义图片列

这一节还演示了新增图片列。

图片列属于列扩展。

它需要做几件事:

  1. 定义图片列类型;
  2. 通过声明合并扩展列注册表;
  3. 定义图片列默认值;
  4. 注册图片列默认值填充逻辑;
  5. 在页面配置里使用 type: "image"

图片列的字段

图片列可以有自己的额外参数。

例如:

ts
imageWidth?: number
imageHeight: number

其中 imageHeight 可以设计成必填。

这样使用图片列时,如果不传高度,TypeScript 会提示。

这就是类型扩展的价值:新增业务列后,也能获得约束和提示。

图片列默认值

图片列可以给宽度默认值。

例如默认宽度为 70

这样页面只需要传必要参数。

常用默认行为由列模块自己补齐。

图片渲染时,可以使用字段值作为图片路径。

如果项目里有静态资源访问前缀,也要在渲染时拼上。

执行注册代码

只定义图片列文件还不够。

注册代码必须被执行。

否则类型扩展和默认值填充逻辑不会真正进入运行流程。

课程里是在页面或入口里引入这个文件,让它执行注册。

等组件包发布后,这里的路径会变成包名。

现在本地还没发包,所以使用相对路径。

使用图片列

页面配置里可以这样使用图片列:

ts
{
  title: "预览图",
  dataIndex: "picUrl",
  type: "image",
  imageHeight: 50,
}

表格会根据 type: "image" 找到图片列默认值和渲染逻辑。

如果宽度没有传,就使用图片列模块里的默认宽度。

图片编辑暂不实现

这一节只实现图片展示。

图片编辑涉及文件上传。

这部分需要更多代码。

所以图片列先不处理编辑态。

这也符合扩展组件的实现节奏:

先把类型、默认值、展示跑通,再补编辑和上传。

这一节的核心

通用表格开始从一个大函数变成模块化系统。

列扩展和模块扩展的边界是:

  • 列扩展:新增字段类型,比如图片列、金额列、状态列;
  • 模块扩展:新增表格能力,比如查询表单、工具栏、批量操作。

主体文件拆分后,后面才能更自然地支持模块添加、模块删除和模块覆盖。

AI Agent 课程学习文档。