聊一聊 React 的 virtual scroll

2020-07-10

This article is a bit old and the content may be outdated, so please refer to it with caution and remember to check the latest official materials (such as documentation, etc.)

也没啥高见,重复一下网上现有的资料而已

Virtual scroll 解决了什么问题 #

Virtual scroll 是绘制超大型列表的性能解决方案。众所周知当 DOM 元素变得很多的时候,渲染元素将消耗可观的时间。即使元素是分配获取、绘制的,浏览器的内存消耗、计算元素位置的 CPU 消耗等也会上升,列表滚动及其他操作的延迟就会增加。

Virtual scroll 是怎么做的 #

正如某相关模块 react-window 的名字,你是通过一扇窗户来看这个大列表,也只有窗户里的东西才会绘制(当然可以设置余量 overscan)。

其他解决方案的问题 #

(没错,我只知道 LazyLoad)

Lazyload #

对于 React 而言,react-lazyload 中需要 lazyload 的元素呆在同一个列表中,这意味着如果一个页面有两个列表,一个的滚动将引起另一个列表内元素的重新计算。而且如果你有一个 overflow: scroll 的列表和一个随页面滚动的列表,将引起事件处理的混乱。

一般来说,LazyLoad 并没有解决 DOM 元素数量级的问题,只是把复杂的元素变得简单,到时候再绘制。如果有大规模筛选列表的操作,Unmount LazyLoad component 也是一笔不小的开支。

如果滚动到很下方,对上面的元素如何处置又是一个问题。Unmount 吧,滚动位置会突变;hide 吧,实质作用并不大,仍然参与计算,内存也占用着。

Virtual scroll 的难点及 React 现有模块简评 #

这里主要针对列表元素高度未知(dynamic,动态,与随时间变化不同),画出来才知道的情况。固定高度的可以简单搞定。

react-window 也是一个好模块,但是动态功能难产,就一笔带过了。

基本问题 #

如何确定每个元素的位置是个难题,如果要求处理 resize (滚回去元素高度会变化)就更难了。

如果你还要求滚过的元素记住状态,对不起,自己实现或者弃坑。

没有浏览器加持,布局受限 #

平常做点网页的效果只需要 CSS 什么的就可以搞定。但是这里全靠手动,还不一定能成功 🤷‍♂️

复杂算法,体积庞大 #

如果还要装作列表是随页面滚动的呢?什么?还要一起滚动的表头和表尾?表头还不能 unmount?……一堆需求下来,就有了体型庞大的 react-virtualized (35 KB min+gzipped) (虽然支持一定程度的 tree-shaking)

性能和懒难以兼得 #

本来就是为了性能才使用 virtual scroll 的(大雾

比如 react-virtuoso 虽然可以支持大小随时间变化,但是估计是加了元素大小变化事件监听,快速滚动下来明显不如 react-virtual 顺畅,甚至可以看到表尾不呆在应该有的底部。

bug 和问题频出 #

(看了 Issue 区就有点劝退了)

简单讲一个 react-virtual 碰到的问题。因为元素高度会变化,在图片加载后让 react-virtual 重新计算。但是重新计算意味着把所有元素的高度都看做是估计值。将会出现多次尝试绘制,再次触发的重新计算甚至引起绘制的死循环。同时滚动会出现跳跃。。😭

Leave your comments and reactions on GitHub