豆皮粉儿们,又见面了,今天这一期,由字节跳动数据平台的躬冯,带你们深刻探索Table组件虚拟化原理及其方案。css
本文做者:躬冯前端
列表及表格的虚拟优化不是个新鲜的课题,近期,团队发现:业界对于table虚拟化居然没有一个相对一劳永逸的解决方案。这是为何?又该如何解决?在本文中,咱们会按部就班的介绍在React+AntDesign技术栈下,团队内部对Table组件虚拟化的不一样实践思路,分析可能遇到的难点问题。react
在列表页、瀑布流、Select组件中,咱们都有可能遇到渲染大数量级列表的场景。在过去,咱们总结了一套卓有成效的办法: 对于列表,咱们经过计算,保证在滚动视窗时,每次只渲染部分元素。这样既减小了首屏压力,也保证长时间加载也不会有更多的性能负担,能够知足上述大部分场景的高性能优化需求。 具体作法上,咱们利用已知的固定行高和滚动偏移量,计算出滚动到的表格行索引,只渲染出有限视窗内所须要的元素,,并对列表进行相应设置,简述过程以下:git
这一方案是普遍被大多数开发人员探讨的。我推荐参考浅说虚拟列表的实现原理,他对问题的思路,实现,均有比较详尽的描述。 现在,咱们的场景来到了AntDesign的Table组件。咱们面对十分相似的问题: 业务当中,Table涉及1000+行&100+列数量级别的渲染时,因为单元格内也具备必定的复杂逻辑,所以页面渲染时长每每须要卡顿5000ms先后的时间。这显然是不可以接受的。 实际上,从长列表到Table组件,咱们的列表无非是从一维轴线上升到了二维平面。所以,所谓表格虚拟化,无非就是但愿表格能够实现: 在兼容Table现有功能的状况下,实现表格只渲染视窗平面内容,对于视窗外的行、列予以隐藏。github
咱们内部的React + umi + AntDesign技术栈是目前前端界较为常见的一种架构基础。AntD做为业界react经常使用组件库,它提供的Table组件,可以方便的帮咱们解决诸多常见需求,包括并不只限于:行选择、行展开、行筛选、数据分页、列固定... 目前AntDesign有AntD@3.26和AntD@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,
}}
/>
...
复制代码
react-window是一个广受欢迎的用于解决表格虚拟化问题的开源代码库,它产出了不一样特性的虚拟化组件,以便用户聚焦不一样虚拟场景的问题解决。api
react-window的原理并不复杂,主要就是本文在虚拟化问题阐述中对于虚拟化要求内容的实现。主要是经过监控onScroll,动态调整表格横轴偏移量,节选适当数量的部分数据,进行可视区内容渲染。性能优化
他的前身是react-virtualized。通过了重构和升级,做者对table和list两种不一样的场景作了更好的抽象,经过重用共通部分的逻辑,实现了更好的性能,代码打包大小也减小到了原先的20%。markdown
看上去,AntD已经给出了一套虚拟化方案。但稍稍深刻调查不难发现,在Github的Issue中,对于官方推荐方案,用户反馈则不甚理想。 好比:antd
针对上述问题,官方均回复以用户自行解决,所以:
按照AntD文档使用虚拟化进行配置,虽然能够必定程度实现虚拟化,但会引发多处表格功能缺失。
这是目前咱们主要亟待解决的问题。
因为官方没有给出很好的虚拟化方案,咱们只好深刻了解AntD内部的架构,寻找问题的切入点。 经阅读代码能够了解,在4.11的AntD/Table代码架构简述以下:
在AntDesign/Table中:
在rc-table中:
至此,咱们能够了解到,AntD架构自己其实已经对表格逻辑进行了必定程度的划分,与data数据的顺序及内容无关的逻辑,已经被单独抽象到了rc-table这个库中。大体上咱们能够理解为下图:
咱们也必需要想清楚:
项目组内对于table的虚拟化工做产生了多种不一样思路:
在不一样业务中,咱们须要的表格组件功能点并不一致,极端场景下,咱们可能只须要使用少许AntD的Feature,且定制化较高。所以咱们能够考虑放弃使用AntD,直接使用rc-table并作必定改造。
实现方式:
方案优劣:
优势:
缺点:
当业务重度依赖AntD的各个功能,不能直接移除AntD。咱们只能保留总体框架,找到切入点作部分改造。因此改造antD内表格scroll事件,使用新的onScroll逻辑:
该逻辑下只修改了AntD传入的data内容,其他操做均基于jsdom手动完成,影响面小,完成度高。但须要仔细测试对各个表格功能点的影响。
方案优劣:
优势:
缺点:
当项目定制化程度较高时,考虑直接放弃antd。方案3保留了AntD的props定义,以保证从AntD迁移时的便利性,随后利用react-table完成大多数功能,虚拟化部分借助了react-window,底层开发了全新的具备虚拟化功能的基础表格。
该方案中引入了github上煊赫一时的开源react hook框架react-table,这是个数据逻辑hook框架,由于排序、选择、等数据逻辑是具备类似性的,因此不必重写,它帮助节省了数据处理的成本,同时也没有干涉UI及虚拟化工做。
方案优劣:
优势:
缺点:
在react-window的README中,能够看到对此问题的描述。当应用虚拟化后,过快的scroll动做会致使占位符还没有更新,只能看到空白内容,须要等少许时间后才加载。当连续快速滚动表格时,呈现不断闪烁空白内容的状态。
问题解决:
当单元格文本内容较长时,但愿高度能够自适应。此需求乍一看彷佛能够利用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以及其数据截取。
问题解决
问题描述:
问题解决:
因为内部资源不能外部分享,因此没有办法把上述方案的产品对外放出。若是您但愿使用带有虚拟化功能的表格,但并不但愿作任何开发工做,这里推荐您几款比较不错的开源产品
rsuite-table
react-base-table
Table组件虚拟化从原理上来讲,是比较单纯的。即使不借助react-window,咱们也看到了同窗们每一个人均可以用本身的思路实现类似的功能需求。
之因此产生方案丛生没法统一的现状,主要仍是因为不一样项目对表格库的要求不一样:有的项目须要低成本,有的项目要求兼容各类不一样feature,有的项目有着繁重的历史包袱。
而一个新的、面面俱到的Table库,又会有替换成本、稳定性风险等问题。所以,因地制宜地对本身项目进行改造,实现一套适合本身项目的虚拟化方案反而是最快速的,最被你们接受的。
所以,本文从三个不一样场景出发,总结了视野可及范围内,你们解决该问题的不一样思路。对于但愿开箱即用的同窗,也给出了几个横向比较下来值得推荐的开源产品。
所谓授人以鱼不如授人以渔,但愿文中探讨的内容,或多或少在提高表格性能及表格虚拟化方向,能给与读者一些启发,为字节系产品的用户带来更快更好的体验。
数据平台前端团队,在公司内负责风神、TEA、Libra、Dorado等大数据相关产品的研发。咱们在前端技术上保持着很是强的热情,除了数据产品相关的研发外,在数据可视化、海量数据处理优化、web excel、WebIDE、私有化部署、工程工具都方面都有不少的探索和积累,有兴趣能够与咱们联系~