探索Table组件虚拟化

豆皮粉儿们,又见面了,今天这一期,由字节跳动数据平台的躬冯,带你们深刻探索Table组件虚拟化原理及其方案。css

0319caf2-aa17-4e88-bfa4-10889cf6f530.gif

本文做者:躬冯前端

前言

列表及表格的虚拟优化不是个新鲜的课题,近期,团队发现:业界对于table虚拟化居然没有一个相对一劳永逸的解决方案。这是为何?又该如何解决?在本文中,咱们会按部就班的介绍在React+AntDesign技术栈下,团队内部对Table组件虚拟化的不一样实践思路,分析可能遇到的难点问题。react

虚拟化问题阐述

在列表页、瀑布流、Select组件中,咱们都有可能遇到渲染大数量级列表的场景。在过去,咱们总结了一套卓有成效的办法: 对于列表,咱们经过计算,保证在滚动视窗时,每次只渲染部分元素。这样既减小了首屏压力,也保证长时间加载也不会有更多的性能负担,能够知足上述大部分场景的高性能优化需求。 具体作法上,咱们利用已知的固定行高和滚动偏移量,计算出滚动到的表格行索引,只渲染出有限视窗内所须要的元素,,并对列表进行相应设置,简述过程以下:git

  • 计算当前可见区域起始数据的 startIndex
  • 计算当前可见区域结束数据的 endIndex
  • 计算当前可见区域的数据,并渲染到页面中
  • 计算 startIndex 对应的数据在整个列表中的偏移位置 startOffset,并设置到列表上
  • 计算 endIndex 对应的数据相对于可滚动区域最底部的偏移位置 endOffset,并设置到列表上

这一方案是普遍被大多数开发人员探讨的。我推荐参考浅说虚拟列表的实现原理,他对问题的思路,实现,均有比较详尽的描述。 现在,咱们的场景来到了AntDesign的Table组件。咱们面对十分相似的问题: 业务当中,Table涉及1000+行&100+列数量级别的渲染时,因为单元格内也具备必定的复杂逻辑,所以页面渲染时长每每须要卡顿5000ms先后的时间。这显然是不可以接受的。 实际上,从长列表到Table组件,咱们的列表无非是从一维轴线上升到了二维平面。所以,所谓表格虚拟化,无非就是但愿表格能够实现: 在兼容Table现有功能的状况下,实现表格只渲染视窗平面内容,对于视窗外的行、列予以隐藏。github

整理现状

AntDesign

咱们内部的React + umi + AntDesign技术栈是目前前端界较为常见的一种架构基础。AntD做为业界react经常使用组件库,它提供的Table组件,可以方便的帮咱们解决诸多常见需求,包括并不只限于:行选择、行展开、行筛选、数据分页、列固定... 目前AntDesign有AntD@3.26AntD@4.11两大版本,后者通过必定的重构和优化,是官方推荐的最新内容。AntDesign3文档中已经删除了对于虚拟化支持的demo(实际也可使用),而在AntDesign4文档中,能看到官方推荐用户以接入react-window的方式解决虚拟化表格问题。 从官方的Demo来看,AntD提供了一个components属性,经过传入一个对象,在其body属性中给到一个ReactWindow提供的虚拟化组件,以实现需求web

...
 // VariableSizeGrid is a component provided by react-window
 const renderVirtualList = (rawData, { scrollbarSize, ref, onScroll })=>
     <VariableSizeGrid 
       {...props}
     />
 ...
 <Table
    {...props}
    className="virtual-table"
    columns={mergedColumns}
    pagination={false}
    components={{
      // overwrite the body set by AntD
      body: renderVirtualList,
    }}
  />
  ...
复制代码

ReactWindow

react-window是一个广受欢迎的用于解决表格虚拟化问题的开源代码库,它产出了不一样特性的虚拟化组件,以便用户聚焦不一样虚拟场景的问题解决。api

react-window的原理并不复杂,主要就是本文在虚拟化问题阐述中对于虚拟化要求内容的实现。主要是经过监控onScroll,动态调整表格横轴偏移量,节选适当数量的部分数据,进行可视区内容渲染。性能优化

他的前身是react-virtualized。通过了重构和升级,做者对table和list两种不一样的场景作了更好的抽象,经过重用共通部分的逻辑,实现了更好的性能,代码打包大小也减小到了原先的20%。markdown

接入时遇到的问题

看上去,AntD已经给出了一套虚拟化方案。但稍稍深刻调查不难发现,在Github的Issue中,对于官方推荐方案,用户反馈则不甚理想。 好比:antd

  • 在问题#21022中提出,使用AntD3后,展开、多选等功能点缺失。
  • 在问题#20339中提出,使用AntD4后,大多数表格功能点都存在缺失。

针对上述问题,官方均回复以用户自行解决,所以:

按照AntD文档使用虚拟化进行配置,虽然能够必定程度实现虚拟化,但会引发多处表格功能缺失。

这是目前咱们主要亟待解决的问题。

rc-table

因为官方没有给出很好的虚拟化方案,咱们只好深刻了解AntD内部的架构,寻找问题的切入点。 经阅读代码能够了解,在4.11的AntD/Table代码架构简述以下:

  • 在AntDesign/Table中:

    • 初始化表格大小和行列内容
    • 初步整理事件和数据
    • 排序、分页、过滤等功能对data和column的计算逻辑
    • 调用rc-table依赖
  • 在rc-table中:

    • 注册各种行列单元格事件
    • 完成各类样式需求,如列固定,行展开
    • 完成渲染

至此,咱们能够了解到,AntD架构自己其实已经对表格逻辑进行了必定程度的划分,与data数据的顺序及内容无关的逻辑,已经被单独抽象到了rc-table这个库中。大体上咱们能够理解为下图:

咱们也必需要想清楚:

  • 这三层逻辑,咱们分别对他们作保留,改造,仍是替换?
  • 如保留,如何解决引入虚拟化逻辑后,虚拟化逻辑对现有框架形成的影响?
  • 如不保留,新的框架如何选择?

组内方案陈列

项目组内对于table的虚拟化工做产生了多种不一样思路:

方案1:基于rc-table不依赖react-window和AntD实现虚拟化

  • 实现思路:

在不一样业务中,咱们须要的表格组件功能点并不一致,极端场景下,咱们可能只须要使用少许AntD的Feature,且定制化较高。所以咱们能够考虑放弃使用AntD,直接使用rc-table并作必定改造。

  • 实现方式:

    • fork一份稳定版本rc-table放入代码库内
    • 按照虚拟化原理对rc-table完成必要改造
    • 本身实现排序、选择、列固定等上层功能点

  • 方案优劣:

    • 优势:

      • 再也不须要本身实现基础的表格呈现,这部分有rc-table完成,咱们只须要针对虚拟化对滚动事件作少许改造。
      • 外部功能feature方便自定制。
    • 缺点:

      • 丢失了AntD的功能基础。

方案2:AntD中截取显示数据,手动销毁表格外dom,手动建立占位符。

  • 实现思路:

当业务重度依赖AntD的各个功能,不能直接移除AntD。咱们只能保留总体框架,找到切入点作部分改造。因此改造antD内表格scroll事件,使用新的onScroll逻辑:

  • 断定dom行位置,计算index
  • setState动做完成后,手动销毁超出视窗的dom内容,同时建立等高空白区域,以维护滚动条位置。
  • 对data数据内容进行裁切并更新,保证视窗内数据的正确性。

该逻辑下只修改了AntD传入的data内容,其他操做均基于jsdom手动完成,影响面小,完成度高。但须要仔细测试对各个表格功能点的影响。

  • 方案优劣:

    • 优势:

      • 向前兼容Antd Table的配置参数,只须要新增少数props,改形成本极低
      • 直接操控dom,开发工做除了数据截取,其他大部分不依赖AntD/rcTable代码,性能有保障。
    • 缺点:

      • 须要当心处理改方案对列固定、行展开等特性的影响。
      • 因为没法提早获取行高,暂不支持直接定位,等价方案是须要先搜索到对应的数据,而后将结果装入InfinityTable

方案3:从新实现表格

  • 实现思路

当项目定制化程度较高时,考虑直接放弃antd。方案3保留了AntD的props定义,以保证从AntD迁移时的便利性,随后利用react-table完成大多数功能,虚拟化部分借助了react-window,底层开发了全新的具备虚拟化功能的基础表格。

  • 框架选择理由

该方案中引入了github上煊赫一时的开源react hook框架react-table,这是个数据逻辑hook框架,由于排序、选择、等数据逻辑是具备类似性的,因此不必重写,它帮助节省了数据处理的成本,同时也没有干涉UI及虚拟化工做。

  • 方案优劣:

    • 优势:

      • 深刻改造,重构程度高,方便后续作任何扩展,方便性能优化
      • 利用开源产品实现原AntD的开发内容,节省必定成本
    • 缺点:

      • 表格基础实现的成本高,基础table须要结合react-table的api从新实现,遇到各类坑都须要本身踩

改造重点问题复盘

空白闪烁

  • 问题陈述:

在react-window的README中,能够看到对此问题的描述。当应用虚拟化后,过快的scroll动做会致使占位符还没有更新,只能看到空白内容,须要等少许时间后才加载。当连续快速滚动表格时,呈现不断闪烁空白内容的状态。

  • 问题解决:

    • 目前没有较好解决办法,增长预载区大小,且优化单元格渲染内容,能够减缓问题严重性。
    • 有同窗提出经过监控scroll时候的speed动态调整预载区大小,不失为一个没实践的思路。

单元格自适应换行

  • 问题陈述:

当单元格文本内容较长时,但愿高度能够自适应。此需求乍一看彷佛能够利用react-window的不定高组件解决,但实际上,该组件须要提供一个高度函数:

// Returns the size of a item in the direction being windowed. 
// For vertical lists, this is the row height. 
// For horizontal lists, this is the column width.
itemSize: (index: number) => number
复制代码

当文本变化时,咱们也须要render后才能获取每一行的高度,因此这个api并无想象中美好。该问题仍是必须借助二次渲染后,经过ref拿dom节点才能解决,但这样会拖慢性能,所以考量后,对此方案不予支持

列固定

  • 问题描述

在方案3中,因为使用了div的flex排版而不是原生table,为了实现列固定时,咱们须要使用三个table来作固定效果,所以滚动时,咱们须要同时改变多个表格的scrollTop以及其数据截取。

  • 问题解决

    • 可使用一个state同步多个table之间的scrollTop,但这种实现有可能由于性能,产生表格渲染的前后,进而产生三张表对不齐的问题。(AntD的Header与body对齐自己也有此问题)
    • AntD不区分三个table,而是在一个table内利用原生tr/td,以及css的sticky特性,部分回避了该问题,但没有解决的很好,fixedcolumn存在的场景下,header和body的同步依旧存有相似问题。
    • 能够将三个表格改成一个表格,而后利用性能最好的3Dtransform来解决此问题。下方推荐的开源产品rsuite-table比较好的经过此方案解决了问题。

列虚拟化

  • 问题描述:

    • 因为目前方案专一于行数据量大的场景,所以当遇到表格列数据量大的场景,依旧存在性能问题。
    • 若是咱们引入react-window的Grid组件进行列虚拟化,会须要兼容子列,列固定等功能,实际实现比想象中复杂。
  • 问题解决:

    • 过多的列从产品设计来讲是不合理的。建议从产品层面改善数据展现问题。
    • 目前暂不支持列虚拟化。

开源表格组件推荐

因为内部资源不能外部分享,因此没有办法把上述方案的产品对外放出。若是您但愿使用带有虚拟化功能的表格,但并不但愿作任何开发工做,这里推荐您几款比较不错的开源产品

  • rsuite-table

    • rsuite.github.io/rsuite-tabl…
    • 虚拟化及表格性能优秀,大多数功能点完备
    • 表头功能薄弱,没有列筛选,没有拖拽改变列宽
  • react-base-table

总结

Table组件虚拟化从原理上来讲,是比较单纯的。即使不借助react-window,咱们也看到了同窗们每一个人均可以用本身的思路实现类似的功能需求。

之因此产生方案丛生没法统一的现状,主要仍是因为不一样项目对表格库的要求不一样:有的项目须要低成本,有的项目要求兼容各类不一样feature,有的项目有着繁重的历史包袱。

而一个新的、面面俱到的Table库,又会有替换成本、稳定性风险等问题。所以,因地制宜地对本身项目进行改造,实现一套适合本身项目的虚拟化方案反而是最快速的,最被你们接受的。

所以,本文从三个不一样场景出发,总结了视野可及范围内,你们解决该问题的不一样思路。对于但愿开箱即用的同窗,也给出了几个横向比较下来值得推荐的开源产品。

所谓授人以鱼不如授人以渔,但愿文中探讨的内容,或多或少在提高表格性能及表格虚拟化方向,能给与读者一些启发,为字节系产品的用户带来更快更好的体验。


数据平台前端团队,在公司内负责风神、TEA、Libra、Dorado等大数据相关产品的研发。咱们在前端技术上保持着很是强的热情,除了数据产品相关的研发外,在数据可视化、海量数据处理优化、web excel、WebIDE、私有化部署、工程工具都方面都有不少的探索和积累,有兴趣能够与咱们联系~

相关文章
相关标签/搜索