为了给表格加虚拟滚动而选了 vxe-table?也可以考虑下这个

年初写过一篇表格选型踩坑,后来又开源了一个 Vue 3 表格组件------长得可以像 ant-design-vue,也可以像 element-plus。

开源这事有个常见的担心:发一篇文章热闹一下,然后就没下文了。

所以先交代进度。这几个月一直在不断迭代,最近这一轮主要是性能:纵向虚拟滚动的几个 O(n) 瓶颈清掉了,横向虚拟化补上了(几百列的宽表滚动不再卡),还做了一套 antdv 兼容类名------迁移的时候老 CSS 不用重写。

先说场景

数据量一上来,表格就开始卡。这事大家都遇到过。

然后你去翻文档,发现几条路都不太顺:

  • ant-design-vue 的,a-table 压根没有虚拟滚动,官方给的答案是分页
  • element-plus 的,得换成 el-table-v2,那是另一套 API、另一套列定义,多级表头和单元格合并还都不支持
  • 于是很多人最后上了 vxe-table

vxe-table 确实能打,功能面也宽,这个我在选型那篇里就写过。但它有两个门槛让人犹豫。

一是心智要重来:列叫 VxeColumn,配置项、事件名、插槽全都得重新学一遍。为了一个虚拟滚动把整个表格层推倒重写,这笔账不是每个项目都划算。

二是使用它的时候,很大可能都要引入完整的vxe-pc-ui

我做 vtable-guild 就是想在中间加一个选项:列定义还是 antdv 那套写法,虚拟滚动是内置的,依赖只有一个。

开箱就有的虚拟滚动

两个 prop,没了。

vue 复制代码
<VTable
  row-key="key"
  :columns="columns"
  :data-source="dataSource"
  :scroll="{ y: 400 }"
  :virtual="true"
/>

virtualscroll.y 要一起给,只写 virtual 不给高度是不生效的------因为算不出视口能放几行。

十万行数据进来,DOM 里始终只有可视区的十来行。滚动的时候换掉这十来行的内容,不是把十万行都塞进文档里让浏览器自己扛。

有一点值得单独说:开了虚拟滚动,行高仍然可以不固定el-table-v2 必须定高;vxe-table 的类型注释里写明「启用纵向虚拟滚动之后将不能支持动态行高」,我在它最新的 4.21.2 上实测过:首屏行高是对的,滚动后新渲染的长文本行会被压回默认行高、内容裁剪------注释和行为一致,除非你用 setRowHeight 把每行高度自己算好喂给它。表格里有换行长文本、富文本单元格的话,这一条大概率能省掉你一堆折腾。

想让它更快也行,行高确定的话给个 rowHeight,走定高快路径。

列很多的那种表

行多是一回事,列多是另一回事。

ERP 的物料明细、BI 的宽表,动辄一两百列。这种表就算开了虚拟滚动照样卡------因为虚拟滚动只管行,列那个维度一个字没管。可见单元格数是「行数 × 列数」,十几行 × 200 列,还是两千多个单元格实打实待在 DOM 里。

所以又加了一个开关:

vue 复制代码
<VTable
  row-key="key"
  :columns="columns"
  :data-source="dataSource"
  :scroll="{ x: 20000, y: 400 }"
  :virtual="true"
  :virtual-column="true"
/>

virtualColumn 只渲染视口里的那十几列,两端固定列照常钉住。200 列的表横着扫过去,渲染列数一直是十几个。

这个开关默认关着,也建议你保持关着,除非列数真的多。列不到五十的时候开它没有任何收益,纯属多做一次表头测量。

另外有几种情况它会自动回落到渲染全部列(dev 环境下控制台会告诉你原因):列上挂了 customCell / customRender(可能返回合并跨度,会破坏列宽对齐)、固定列没有分列两端、showHeader: false 且有非数字列宽(没有表头可量)。踩到了不会报错也不会错位,就是不优化而已。

还有一个已知边界:列滚出视口时,挂在该列表头上的筛选面板、tooltip 会跟着一起卸载。多数表格感知不到,但如果你的交互依赖「横向滚动时筛选面板保持打开」,先知道这条。

列宽倒是不用你操心,auto、百分比都行,宽度是从表头量出来的,不要求你写死数字。

列定义不用改

这是我最想说的一条。

从 antdv 迁过来,columns 基本一行不用动:

ts 复制代码
const columns: TableColumnsType<UserRow> = [
  { title: '姓名', dataIndex: 'name', key: 'name', width: 180 },
  { title: '年龄', dataIndex: 'age', key: 'age', width: 96, align: 'right', sorter: true },
  {
    title: '状态',
    dataIndex: 'status',
    key: 'status',
    filters: [
      { text: '在岗', value: 'active' },
      { text: '休假', value: 'paused' },
    ],
    onFilter: (value, record) => record.status === value,
  },
]

dataIndexsorterfilters / onFiltercustomRenderfixedellipsis、多级表头的 children,还有受控/非受控双轨(sortOrderdefaultSortOrder 都认),全是照着 antdv 抄的。

有一个地方不一样,得提前说:change 事件没有分页那个参数 ,签名是 (filters, sorter, extra)。因为这个库不内置分页,页码这块得你自己在页面层接。

老的覆盖 CSS 还能继续用

做过迁移的都知道,最烦的往往不是组件 API,是那堆积年累月写下来的样式补丁:

css 复制代码
.ant-table-thead > tr > th {
  background: #fafafa;
}
:deep(.ant-table-cell) {
  padding: 8px 12px;
}

换个组件,这些全废了,得一条条重写。

现在可以不重写。全局开一个开关:

ts 复制代码
app.use(
  createVTableGuild({
    compatClass: true, // 默认关闭
  }),
)

开了之后,组件会在原有元素上额外挂一套 ant-table-* 类名------ant-table-wrapperant-table-theadant-table-cell 这些结构类,ant-table-small / ant-table-bordered 这种变体类,还有 ant-table-row-selectedant-table-cell-fix-left-last 这类状态类都有。你那些老选择器就直接命中了。

DOM 结构不变,也不引入任何样式,就是多几个 class。开关前后我对着截图逐字节比过,像素没有变化。

有人可能会担心跟 antdv 自己的样式打架。一般不会:antdv 4.x 走 cssinjs,每条规则的选择器里都注入了 hash 类,实际长这样:

css 复制代码
.ant-table-wrapper.css-dev-only-do-not-override-xxxxx .ant-table-thead > tr > th { ... }

我们的元素不带那个 hash,所以 antdv 的样式匹配不到我们,而你手写的选择器能正常命中------正好是想要的效果。我在同时加载 antdv 的页面上验过,178 条 ant-table 规则,命中我们元素的一条都没有。

唯一的例外是你用了 <StyleProvider :hashed="false">,那 antdv 就不生成 hash 了,选择器会退化成能匹配到我们的形式,这种情况别开这个开关。

顺带说明白:这是迁移期的过渡手段,不是我承诺的稳定 API。后面 DOM 结构调整了这些类名可能会变。建议当成「先跑起来」用,别当长期方案。

换一套皮

同一套 columns 和数据,改一行配置,整套视觉跟着换:

ts 复制代码
app.use(createVTableGuild({ themePreset: 'element-plus' }))

表头、边框、行高、排序图标一起变,不用额外 import 任何 CSS。

项目里本来就是 element-plus 的生态,这条能让表格看起来不违和;反过来也一样。

改样式不用打补丁

想改某个位置的样式,不用去猜类名然后写 !importantui prop 精确到 slot:

vue 复制代码
<VTable :ui="{ th: 'bg-slate-100 text-slate-700', td: 'py-3' }" />

三层覆盖:默认主题 → 全局配置 → 实例 props,越往后优先级越高。全局统一规范,个别表格开例外,两件事不冲突。

CSS 变量那一层也开着,暗色模式之类的直接改 token 就行。

其他功能

一次性列完,都是常规能力,文档站每一项都有能直接点的 demo:

排序(多列、受控双轨)、筛选(多选 / 单选 / 树形 / 搜索 / 自定义面板)、行选择(checkbox / radio / 批量菜单 / checkStrictly)、展开行、树形数据、固定列、固定表头、多级表头、单元格合并、列宽拖拽、title / footer / summary、sticky、内置中英文 locale。

单元格编辑可以用 bodyCell 插槽自己组合,但没有内置的编辑状态和校验引擎。

什么时候别用它

说在前面,省得你集成到一半才发现:

  • 要分页:没有内置,得自己接
  • 要完整编辑引擎、Excel 导出、列拖拽换序、右键菜单:这些还是 vxe-table 的主场,它在这块的完备度我短期追不上
  • 有无障碍合规要求:排序头和筛选触发器目前还不能键盘操作
  • 就一张几十行的普通表 :原生 a-table / el-table 够用,不用多引一个依赖

装一下

bash 复制代码
pnpm add @vtable-guild/vtable-guild
ts 复制代码
// main.ts
import { createVTableGuild } from '@vtable-guild/vtable-guild'
import '@vtable-guild/vtable-guild/css/style'

createApp(App).use(createVTableGuild()).mount('#app')

不需要装 Tailwind,也不强制装 antdv 或 element-plus。运行时依赖就一个 tailwind-variants,一个包一份样式,没有配套 UI 库要一起引。

项目里已经有 Tailwind 的话,把 cssMode 切到 tailwind3 / tailwind4,内部 class 就不带前缀,你直接写 px-2 就能覆盖。

最后

相关推荐
开开心心就好9 小时前
批量提取PDF中的图片,直接导出原图
前端·javascript·支持向量机·智能手机·pdf·html·启发式算法
Setsuna_F_Seiei11 小时前
前端转型 Agent 开发 05 之 Agent Hooks 与 Checkpointer(让 Agent 从全自动转变人为可掌控)
前端·agent·ai编程
百万蹄蹄向前冲13 小时前
风扇转了一晚上MVP专家团翻车事故
前端·人工智能
默_笙13 小时前
🏛 给 AI 配一间办公室:Harness Engineering 六大模块与它的实现
前端·javascript
linux_cfan15 小时前
videojs v10 源代码系列解读:14 · 谓词守卫:在运行时安全地调用能力
前端·javascript·音视频
kyriewen15 小时前
我扒了 10,221 条 JD:腾讯技术岗 75% 在要 AI
前端·人工智能·ai编程
郑州光合科技余经理16 小时前
同城外卖小程序开发:下单成功后,后台导出能不能对上用户端状态
开发语言·前端·git·后端·uni-app·php·ai编程
+VX:Fegn089516 小时前
计算机毕业设计|基于springboot + vue旅游管理系统(源码+数据库+文档)
java·开发语言·vue.js·spring boot·课程设计
雪芽蓝域zzs16 小时前
第 7 章:双 Y 轴组合图(柱状 + 折线,看板高频场景)
vue.js·echars
IT_陈寒17 小时前
Vue的响应式让我熬到凌晨三点,原来漏了这个小细节
前端·人工智能·后端