logoProsperBao

虚拟表格的一些问题

2023-01-09 02 · 15min

如何遇到的问题

在开发公司项目的时候, 遇到了一个表格处理数据量非常庞大, 可能有 30k 以上的数据, 表格列理论上可以是无限的, 刚开始的时候很天真, 虚拟表格一把梭. 结果碰到了不少的问题, 今天就来记录这些问题.

虚拟表格跨行合并的问题

  1. 表格的性能
  2. 跨行合并的问题
  3. 表格的数据量
  4. 表格内容的差异化以及鼠标手势的响应
  5. 表格的样式
  6. 实时动态表头

新路历程

表格的性能问题, 一般来说, 会有两个方面的问题, 一个是表格的渲染, 一个是表格的数据处理.

数据处理方面由于直接在内存中计算, 会比表格渲染快速几个量级级, 所以一般来说, 数据处理的问题不会太大.

但是到了表格的渲染, 问题就多了, 第一个频繁变更的表头和数据量导致每次操作表格需要变更的 dom 节点数量都会很大, 导致渲染的性能问题.

一开始是直接使用 vue2 的虚拟表格, 但是发现如果表格内容差异化和鼠标手势响应在数据列增加到 8 个以上, 卡顿的问题就会很明显.

所以换了几个虚拟表格库, 结果都不是很理想, 因为在 vue2 中 diff 是 diff 到底, 导致一旦某个父元素变动, 子元素全部会变动, 导致 dom 操作过多.

表格在 dom 变动的时候页面是直接卡死不能动并且时间可能会非常的长, 还有一个就是 key 的问题, 一般来说直接变动表格的数据, 表格会重新渲染.

但是在实际使用的时候基本没有库能够做到这一点, 导致表格的数据变动, 但是表格的渲染没有变动, 导致表格的数据和表格的渲染不一致.

这时候就要使用 key 去标识节点被更改变动了, 表格才会重新渲染, 这时候又回到了 diff 的问题, vue2 是直接 diff 到底的, 所以即使是变更了一个父元素的组件, 子组件并不需要变动, 但是也会重新渲染.

在大面积的数据变动或者表格内容联动的时候, 会导致表格的渲染非常的慢, 甚至会卡死.

所以直接抽离项目使用 iframe 嵌入, 直接另起一个 vue3 的项目, 然后直接 Element Plus 虚拟表格 一把梭.

在这里数据列增加到 10 个以内 Element Plus 虚拟表格 大数量级的情况下表现得非常优异, 但是到了 10 个以上, 就会出现卡顿的情况.

这个就是无效的 popover 导致的, 因为需要响应不同的鼠标手势和悬浮显示操作内容, 所以大部分都是需要 popover 来承接单元格操作内容.

但是在不触发的情况下有很多 popover 是不需要渲染的, 但是在不触发 popover 的情况下会保留 popover 的部分 dom 节点, 即使不触发 popover 也会占用内存.

这里也就是为什么数据列数一多, 表格就会卡顿的原因.

在查看了 Element Plus 虚拟表格 属性和代码之后发现, Element Plus 虚拟表格 只针对了行的渲染优化, 但是没有针对列的渲染优化.

所以导致列一旦多了, 单元格自定义内容就会很多, 并且在一次性添加数据的时候, 会导致表格卡顿.

所以得更换具有列渲染优化的虚拟表格库.

之后更换到了 vxe-table 发现确实没问题了, 但是随之而来又发现了小问题, 并且 issue 的量偏多, 解决速度很慢, 每次版本升级都会造成一些功能失效或者组合失效, 阅读代码的时候并没有看到单元测试. 所以更换朋友推荐的 ad-grid.

问题解决

  • 表格的性能
  • 表格的数据量
  • 实时动态表头

以上三个问题在更换成 ad-grid 的时候就得以解决, 在数据量非常庞大的情况下(30列-30万行), 除了在拖动滚动条的时候会卡顿, 其他的操作都是非常流畅的.

表格内容的差异化以及鼠标手势的响应

在上面新路历程里也说了 popover 的问题, 因为需求的定制化 popover 非常多, 并且需要响应不同的鼠标手势, 关闭方法, 所以无法使用组件库提供好的 popover.

所以只能自己动手封装所有的自定义化操作都通过一个 popover 然后根据属性加载不同的 Component 来实现.

popover 只和单元格需要自定义化的内容触发挂钩, 也就是没有多余的 dom 节点占用内存.

通过自定义 popover 组件, 然后再通过 column 数据结构上附加额外的 popover 需要的属性来分发挂载自定义组件.

这样单元格的组件和挂载到 popover 的组件就可以很清晰的分开以及复用.

跨行合并的问题

在所有虚拟表格库中, 都是支持跨行合并的, 但是都有几个很隐蔽的缺点:

  1. 跨行合并的单元格如果过多, 超过虚拟渲染的数量, 就会导致顶级的跨行合并单元格消失, 然后合并单元格失效
  2. 合并 fixed 的单元格, 会导致合并失效并且虚拟滚动会消失
  3. 如果跨行合并过多, 虚拟滚动会消失

所以, 在跨行和合并的时候, 想了各种办法, 终于想到一个骚操作.

如果我在每一个需要向下合并的行都计算出高度以及向上平移的高度,通过 css 对每一个合并的单元格进行平移,那么不就能解决问题了吗? 虚拟表格的行高都可以通过参数来控制.

所以我又针对之前使用过的表格做了一个简单的测试, 结果如下

  • Element Plus 可以合并, 但是还是有一个问题, 列需不支持虚拟
  • vxe-table 可以合并没问题, 但是控制不了最小渲染行数, 并且 fixed 的时候会导致合并失效
  • ad-grid 没毛病, 性能也非常优秀

所以在经过非常长时间的调整和修改之后, 最终的表格组件就是 ad-grid 了.

附上 Element Plus 使用后的简单 demo, 算是复盘一下. ad-gridElement Plus 的使用方式是一样的, 只是 ad-grid 的性能更好.

示例

Element Plus

Ag-gridvxe-table 其实和 Ag-grid 差不多,性能会和功能丰富性都会稍差点,不过文档非常友好,暂时不添加进示例

对比性能

以后还有问题再继续记录