虚拟列表——你需要知道的优雅处理大数据渲染的技巧
2023年了,你还不会高性能渲染吗?1万?10万?条数据,根本不怂,虚拟列表高性能渲染实战!
# 前言
按照我们常规理解,数据量较大时一般都是通过分页进行解决处理,对于Table
组件类这种列表展示我们应该很熟悉了。
分页可以实现最小化接口数据量,后端根据前端传入的参数,返回指定的范围数据。毕竟我给你1000条数据,你也很难用一屏把全部数据展示出来(问就是“优雅”🤡)
就一般场景而言,以后端分页为主。但是,也不排除一些特殊业务场景,需要返回大量数据的情况(问就是“业务场景”需要🤡)
这里我们主要讨论长列表的场景,当存在在大量数据返回的情况下(如 Select
组件),如何优雅的高性能渲染
# 背景
结合我司实际使用场景为例:
有这样一个类似 Input
+ Select
组件的高级业务封装组件,可选列表数据根据输入内容动态变化,每一项嵌套有多个div
、span
标签以及img
等元素,整个容器元素会频繁关闭、开启,触发重新渲染,需要渲染的字典数据接口是全部返回的,一般是600条以上。
简单分析下:
- 接口返回数据量较大,考虑到列表每一项存在多个嵌套标签元素,在创建节点、渲染数据的时候会比较消耗性能
- 列表数据动态变化、频繁开关,这意味着整个元素会频繁触发创建节点、渲染的流程
# 分析
为了更加清晰的表现出耗时瓶颈,这里我以1万条数据为例,分析创建一万个节点的消耗占比
根据耗时结果,分析可得出:
- js运行时间为23ms,还是比较快的
- 总运行时间达到了739ms,耗时比较明显
这俩耗时差距明显,我们需要思考下发生了什么
总所周知,JS的运行是单线程的,浏览器为了能够使得JS内部task
与DOM
任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染,流程大致是这样🤓
task->渲染->task->...
如果存在微任务的话,则是这样子🧐
宏任务 -> 微任务 -> 渲染 -> 下一个任务...
具体细节不再赘述,详情参见我的这篇文章 从宏观层面理解——浏览器中JavaScript的运行机制
根据JavaScript的执行逻辑,结合Chrome performance工具可以进一步分析得出如下结果:
耗时主要集中在 Rendening
,为688ms,占比高达 93%。进一步查看performance,可以发现Recalculate Style
(样式重新计算)与Layout
(布局)的耗时占比,可以确定性能瓶颈主要集中在 渲染 这一步。
这是没有经过任何优化处理的代码,在拿到10000条数据后,我们直接创建10000个DOM节点。
假如弹窗的宽高是固定的,如果弹窗内只能展示20条数据,接口返回给了10000条数据,那么创建10000个DOM节点再全部渲染是完全没必要的,就像做图片懒加载一样,我们可以在页面滚动到可视区域的时候再渲染对应的图片。
因此,我们应该如何优化?
首先要考虑的应该是尽量做到最小开销的渲染。那么答案就很明显了,减少节点的创建,进而减少布局的渲染消耗。