虚拟滚动 - 什么是虚拟滚动?

1,913 阅读6分钟

什么是虚拟滚动?

下面这张图说得很明白: 正常的滚动是指在滚动的容器中渲染出所有的元素, 虚拟滚动是指在滚动的容器只渲染可视区域内的元素,非可视区域内的元素不进行渲染

我们为什么需要虚拟滚动?

因为性能元素,当我们需要的页面需要渲染大量的元素时,虚拟滚动通常能够提升我们页面的性能

一个虚拟滚动的测试场景

测试页面:

虚拟滚动测试页面.png

测试的方案: 测试页面

startTime定义: 点击按钮这一刻的时间

endTime定义: 弹窗组件最后变化的时间,即 useEffect({})

测试的结果(单位ms):

数据量正常 table(首次)正常 table(非首次)虚拟滚动(首次)虚拟滚动(非首次)
10076637670
5001881426471
10003362188379
2000586368108102
3000841548142102
50001348796205144
1000026091531375234
2000052123127782409
30000780748381019523
500001297282381909933
100000268281814337192140

测试结论

  1. 当数据量在 100 时,虚拟滚动的性能和正常 table 的性能是差不多的
  2. 当数据量 > 100 时,虚拟滚动的性能要优于正常 table 的性能
  3. 当数据量到达到 10W 时,尽管虚拟滚动相较于正常 table 性能由很大的提升,但是速度还是非常慢的,打开 dialog 还是需要 3.7s 左右的时间

如何使用虚拟滚动?

虚拟滚动的原理

如果真的想要了解原理,推荐看这两篇文章,讲的很清晰了,这里就不展开说明了

  1. 新手也能看懂的虚拟滚动实现方法
  2. 前端长列表滚动方案探讨,看完不要再说不知道怎么实现虚拟列表了

一些常用的虚拟滚动库

react-window

用于高效呈现大型列表和表格数据的 React 组件 使用场景: 在场景/列表中,如果需要渲染大量的数据,我们可以使用这个库

react-window使用场景.png github react-window

official document

react-virtualized-auto-sizer

一个自动获取父容器宽高的库 使用场景: 这个是一个工具组件,当我们需要监听父容器的宽高变化时,且需要实时获取 width,height 属性时,我们可以使用这个库

github react-virtualized-auto-sizer

react-window-infinite-loader

受 react-virtualized 启发的 InfiniteLoader 组件,可与 react-window 一起使用 使用场景: 一般使用搜索结果页

react-window-infinite-loader使用场景.png

github react-window-infinite-loader

react-vtree

该软件包为渲染大型树结构提供了轻量级的灵活解决方案。它建立在 react-window 库之上。 使用场景: 需要渲染 tree 数据结构,且该结构数据量很大时,我们可以使用这个库

image.png github react-vtree

antd design 中的虚拟滚动

antd@5 中下面组件中使用到了虚拟滚动

  1. Select
  2. TreeSelect
  3. Table
  4. Tree

思考:我们是否真的需要虚拟滚动?

结论:除非存在明确的需求需要使用虚拟滚动去提升性能,否则不推荐使用虚拟滚动

项目维护角度

从项目迭代维护的角度,一般的原则是:在满足场景需求时,代码和实现方案越简单越好,可读性越高越好,虚拟滚动本身是会在增加代码的复杂性的 以 table 场景举例,直接 antd 能够兼容大部分的业务场景,同时也很容易应对各种需求变化,比如表格的合并,固定行列等等;但是如果使用了虚拟滚动的话,表格合并,固定行列都需要重新去思考怎么封装,开发的成本会增加很多

合理性

从合理性的角度,场景是否真的需要展示这么大的数据量 针对于数据的展示,其实是由通用的降级解决方案的:

  1. 分页
  2. 搜索选择

在使用虚拟滚动之前,应该去思考:常用的降级方案带来的交互性是不是会更好

使用虚拟滚动的注意点

当然,不排除某些场景我们一定要使用虚拟滚动,比如:

  1. 商品展示页的无限下拉加载
  2. 推荐流场景
  3. 通讯录
  4. 大型 tree 结构的展示

在这些通用场景,确实使用虚拟滚动能够带来更多的合理性和更好的交互,但是使用虚拟滚动技术本身是存在限制的, 之前也提及了,虚拟滚动本身的交互拓展性是存在困难的,可能需要自己的二次开发,如果要做特别的交互或者要做特别的场景化定制,可能会存在困难

因此在使用虚拟滚动场景时,一定要沟通好交互方式和展现形式,提前判断开发的难度

QA

Q: 虚拟滚动为什么比传统的渲染性能要好呢?

A:

针对大量的数据,渲染的瓶颈在于我们需要渲染大量的 DOM;这些 DOM 很大部分都是在不可见区域内的,同时渲染这部分 DOM 耗时又非常高; 因此我们可以采取一个方案:通过 js 的计算,只渲染目标区域的 DOM

在这个过程中:js 判断的耗时 < 渲染大量 DOM 的耗时 因此虚拟 DOM 能够提升渲染的性能!

Q: 虚拟 DOM 更新的原理是什么?

A:

  1. 滚动事件发生之后,获取滚动之后的距离 offset
  2. 将 offset 更新到 this 上去,set 操作会自动触发 this.render
  3. 在 render 中我们计算出需要渲染的 DOM 内容,然后将其更新到页面中

参考链接

  1. react-window
  2. react-window-demo
  3. 使用 react-window 虚拟化大型列表
  4. 新手也能看懂的虚拟滚动实现方法
  5. 前端长列表滚动方案探讨,看完不要再说不知道怎么实现虚拟列表了
  6. 文章 demo 仓库