跳到正文

我所理解的前端框架(React 和 Vue)最佳实践(1):用表格组件减少通用渲染和交互的维护成本

我所理解的前端框架(React 和 Vue)最佳实践

  1. 用表格组件减少通用渲染和交互的维护成本(当前)
  2. 利用派生值减少维护成本和心智负担

通用渲染和交互适合交给表格组件

一张合同列表要展示人物、所属战队和到期日。用户可以按到期日排序,再按赛区筛选。表格组件可以生成表头、画出排序状态、提供筛选控件。至于没有公开到期日的人排在哪里、筛选查的是整份名单还是已经取回的一页,这些要由项目决定。

职责 组件库可以接管的部分 项目要决定的部分
表格结构 按列定义渲染表头、行和单元格,提供空态和自定义内容入口 字段含义、列顺序、日期显示、窄屏保留哪些信息
表格交互 管理排序、筛选、勾选、展开和分页的控件与状态 筛选或翻页后如何处理已选项,整行与行内链接各自去哪里
数据处理 对已拿到的数据应用行模型,或接收项目控制的查询状态 由客户端还是服务端处理排序和筛选,接口如何取数、分页和授权
长列表渲染 测量行高、计算可见窗口、生成虚拟占位 使用哪个滚动容器、提供稳定的行 ID、确定取数范围和首屏策略

展示型目录只有几列、链接和少量数据时,原生表格通常就够了。行很多而交互依然简单,可以保留原生结构,只把可见窗口和行高测量交给虚拟化工具。虚拟滚动减少的是渲染的行数,数据下载量取决于接口给了多少记录。

后台有多张表都要分页、勾选、排序和列筛选时,我会先试项目已有组件库的完整表格。需要共用列定义、排序和选中状态,页面又有自己的标记结构或交互方式时,可以选无头表格引擎,让它管理列和行模型,项目负责渲染和交互。

Vue 表格优先沿用已有组件库

  • Element Plus 的 ElTable 可以覆盖排序、筛选、勾选和固定列等常见后台需求,分页可以搭配 ElPagination。要虚拟化时单独评估 ElTableV2。它仍标为 Beta,列配置和尺寸接口也和普通表格不同,已有列插槽和功能要逐项核对。 Element Plus 表格 · 虚拟化表格
  • Naive UI 的 NDataTable 在同一个组件中提供分页、筛选和虚拟滚动。选它之前先确认表格能否设置内部滚动区的高度,以及虚拟模式下是否需要跨行合并。官方文档注明开启 virtual-scroll 时 rowSpan 不生效。 Naive UI DataTable
  • Nuxt UI 的 UTable 在 TanStack Table 上封装了表格渲染、主题、单元格插槽和 Vue 状态绑定,用 TanStack Virtual 处理虚拟行。需要让表格随外部内容区滚动时,可以传入滚动容器。纯 Vue 项目也有官方 Vite 接入方式。 Nuxt UI Table · 外部滚动实现 · Vue 安装文档

React 表格按交互需求和定制范围选择

  • Ant Design Table 适合已经使用 Ant Design 的管理页面。排序、筛选、分页、勾选和展开可以从同一个表格入口配置,远程查询时把表格变化接到项目的请求逻辑。它也提供响应式列和虚拟列表。启用虚拟模式时,scroll.x 和 scroll.y 都需要是数字,页面滚动有特殊要求时要用实际布局试一次。 Ant Design Table
  • MUI X Data Grid 适合已经用 Material UI,且需要网格式编辑、键盘操作或更完整的数据网格能力的项目。基础版本包含排序、筛选、编辑和虚拟化。固定列属于 Pro,行分组属于 Premium。先按实际功能核对许可,再确定用哪个包。 MUI X Data Grid · 功能与许可
  • TanStack Table 适合表格样式和交互要深度定制,同时想复用列定义、筛选、分页、勾选和行模型的项目。Vue 和 React 都有适配器。以 React 为例,@tanstack/react-table 负责表格模型,项目渲染表格结构和控件,需要虚拟滚动时再接虚拟化库。 TanStack Table · 虚拟化指南

人物列表用 UTable 接管渲染与虚拟行

LoL 职业库的人物列表用来浏览档案。桌面显示六列,窄屏保留人物、所属战队和合同到期日,把位置或职务挪到姓名下面。纵向滚动跟随页面的主内容区。点击姓名或一行的空白处打开人物档案,战队链接通向对应战队。

接入前,人物页自己写表头和单元格,共用的 TableRows 和 LinkedRow 分别处理虚拟行和整行导航。换成 Nuxt UI 4.12.0 的 UTable 后,页面把六列的定义和内容交给组件,通用表格结构与虚拟行由它生成。

职责 UTable 接管 人物页的适配
表格结构 生成表头、行和单元格外壳 改写为列定义和单元格插槽,继续渲染原有头像、战队链接和合同日期
虚拟滚动 测量行高、计算可见窗口、生成上下占位行 传入页面滚动容器和 scrollMargin,设置首屏窗口和稳定行 ID
窄屏布局 按列模型渲染可见列,计算占位单元格的跨列数 CSS 处理窄屏首屏,水合后同步实际可见列
行导航 生成真实表格行与单元格 保留原生链接,在行空白处转发点击,兼顾中键、修饰键和文本选择

窄屏的难点出在首屏。服务端输出六列,CSS 先隐藏其中三列。要是虚拟占位单元格还带着 colspan="6",浏览器就可能多分出空列,把姓名挤窄。人物页先隐藏这个单元格,只让占位行撑出高度。水合后,客户端把断点结果交给列模型,由组件计算实际跨列数。

整行导航也需要单独适配。真实的 <tr> 由 UTable 生成,人物页在姓名链接挂载后把它登记为该行的主链接。点击空白处时才转发,点战队链接或选中文字时保留原有行为。人物搜索和赛区筛选也仍由页面处理,表格接收筛选后的结果。

试验记录里的代码与资源成本

试验时有 505 条记录,桌面和窄屏都能滚到末尾,服务端仍输出最前面的 24 条。人物页组件从 144 行增加到 247 行,新增的主要是列定义、外部滚动偏移、断点同步和行导航适配。原有共享组件仍供合同列表等页面使用,所以这次只少了人物页对它们的调用。

构建上新增了 Nuxt UI 模块和依赖。为保留网站的外观,项目启用了 theme.unstyled,表格继续使用原有的公共样式,人物页补上列宽和行交互样式。

在冷缓存下打开构建后的页面,对实际请求的 JS 文件逐个做 gzip 后加总,接入前约 183 KiB,接入后约 236 KiB。统计包含 Nuxt 预取的其他路由资源,因此这组数字只用于比较整次页面访问的资源开销。

只看这一张展示表,接入多了代码和依赖,但也把行高测量、虚拟窗口和通用表格渲染交给了 UTable。对这个项目,这项交接有价值。如果以后有多张表要统一排序、展开或列状态,还能用同一套接口。试用时先把滚动宿主、窄屏和链接契约跑通,才能知道这些共用能力值得付出多少适配成本。