豆皮粉儿们,又见面了,明天这一期,由字节跳动数据平台的躬冯,带大家深刻摸索 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