豆皮粉儿们,又见面了,明天这一期,由字节跳动数据平台的躬冯,带大家深刻摸索Table组件虚拟化原理及其计划。

本文作者:躬冯

前言

列表及表格的虚构优化不是个陈腐的课题,近期,团队发现:业界对于table虚拟化居然没有一个绝对一劳永逸的解决方案。这是为什么?又该如何解决?在本文中,咱们会循序渐进的介绍在React+AntDesign技术栈下,团队外部对Table组件虚拟化的不同实际思路,剖析可能遇到的难点问题。

虚拟化问题论述

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

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

这一计划是宽泛被大多数开发人员探讨的。我举荐参考浅说虚构列表的实现原理,他对问题的思路,实现,均有比拟详尽的形容。
现在,咱们的场景来到了AntDesign的Table组件。咱们面对非常相似的问题:
业务当中,Table波及1000+行&100+列数量级别的渲染时,因为单元格内也具备肯定的简单逻辑,因而页面渲染时长往往须要卡顿5000ms前后的工夫。这显然是不可能承受的。
实际上,从长列表到Table组件,咱们的列表无非是从一维轴线回升到了二维立体。因而,所谓表格虚拟化,无非就是心愿表格能够实现:
在兼容Table现有性能的状况下,实现表格只渲染视窗立体内容,对于视窗外的行、列予以暗藏。

整顿现状

AntDesign

咱们外部的React + umi + AntDesign技术栈是目前前端界较为常见的一种架构根底。AntD作为业界react罕用组件库,它提供的Table组件,可能不便的帮咱们解决诸多常见需要,包含并不仅限于:行抉择、行开展、行筛选、数据分页、列固定...
目前AntDesign有AntD@3.26和AntD@4.11两大版本,后者通过肯定的重构和优化,是官网举荐的最新内容。AntDesign3文档中曾经删除了对于虚拟化反对的demo(理论也能够应用),而在AntDesign4文档中,能看到官网举荐用户以接入react-window的形式解决虚拟化表格问题。

从官网的Demo来看,AntD提供了一个components属性,通过传入一个对象,在其body属性中给到一个ReactWindow提供的虚拟化组件,以实现需求

 ... // 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是一个广受欢迎的用于解决表格虚拟化问题的开源代码库,它产出了不同个性的虚拟化组件,以便用户聚焦不同虚构场景的问题解决。

react-window的原理并不简单,次要就是本文在虚拟化问题论述中对于虚拟化要求内容的实现。次要是通过监控onScroll,动静调整表格横轴偏移量,节选适当数量的局部数据,进行可视区内容渲染。

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

接入时遇到的问题

看上去,AntD曾经给出了一套虚拟化计划。但稍稍深入调查不难发现,在Github的Issue中,对于官网举荐计划,用户反馈则不甚现实。
比方:

  • 在问题#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

    • http://rsuite.github.io/rsuit...
    • 虚拟化及表格性能优良,大多数性能点齐备
    • 表头性能单薄,没有列筛选,没有拖拽扭转列宽
  • react-base-table

    • https://github.com/Autodesk/r...
    • 自带虚拟化,根本功能齐全,其余性能与AntD相比有肯定差距

总结

Table组件虚拟化从原理上来说,是比拟单纯的。即使不借助react-window,咱们也看到了同学们每个人都能够用本人的思路实现类似的性能需要。

之所以产生计划丛生无奈对立的现状,次要还是因为不同我的项目对表格库的要求不同:有的我的项目须要低成本,有的我的项目要求兼容各种不同feature,有的我的项目有着沉重的历史包袱。

而一个新的、八面玲珑的Table库,又会有替换老本、稳定性危险等问题。因而,就地取材地对本人我的项目进行革新,实现一套适宜本人我的项目的虚拟化计划反而是最疾速的,最被大家承受的。

因而,本文从三个不同场景登程,总结了视线可及范畴内,大家解决该问题的不同思路。对于心愿开箱即用的同学,也给出了几个横向比拟下来值得举荐的开源产品。

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

The End