在项目开发中,有时候遇到需要展示海量数据展示的场景,也就是长列表渲染
目前在做的项目中列表场景使用此方案处理了下
使用之前页面稍微上滑刷新几页基本内存就是翻倍的增长
对用户使用体验不佳
长列表渲染大量节点导致的性能问题
-
首次渲染的时候会一次性渲染大量的节点,占用大量的gpu资源,页面滚动操作等会有明显的卡顿。
-
即使我们使用一些分片加载或者上滑加载的方案优化,但是渲染出来的dom依然会占用大量内存,节点越多,性能影响越大。
虚拟列表,顾名思义,就是不实际的去展示这个列表,核心思路就是只展示用户实际这个节点应该看到的数据,其余的部分虚拟化,同时不影响用户对原列表操作(滑动,滚动)上的体验。
虚拟列表主要包含三个区域,虚拟区/缓冲区/展示区,
目前使用比较多的虚拟列表库是 react-window,
下图是它的dom展示形式。
参考这个我们自己去实现类似的功能,同时适配我们自己的场景(下拉加载,上滑刷新等)
高度固定的虚拟列表
每个item是固定的这种好处理些,简单说有三种实现方案
-
添加一个将 items 往下推到正确位置的空元素,动态计算推下去的距离
-
使用 transform 方案
-
使用定位的方案,类似react-window的方案
固高代码实现
首先定义一个子组件
子组件接受 containerHeight, itemHeight, itemCount, children, InfiniteItem 四个参数
containerHeight : 容器 高度
itemHeight:每个元素的高度
itemCount:目前长列表的总数据量
children:item的 render dom
InfiniteItem:上滑加载组件
demo效果效果如下
demo 地址:codesandbox.io/s/fixedsize…
可以看到列表页面实时更新,右侧的dom固定不变
首先我们要计算出渲染的开始项和结束项,
然后根据我们计算出来的值限制需要展示的 Component
设置一个缓冲区就是上下多渲染几个item,减少滑动的时候白屏时间
计算出需要预留的div高度,推一个空的元素占位着,这样不影响右侧滚动条的滚动的准确位置。
flushSync:允许你强制 React 在提供的回调函数内同步刷新任何更新,这将确保 DOM 立即更新。
文档地址: zh-hans.react.dev/reference/r…
处理渲染异步导致的白屏现象 改成同步更新,同时做 节流 + RAF 优化
动态列表高度
这种和固定高度不同,不能再传递固定的高度了,需要把每一项的高度都计算出来,再去处理展示的逻辑
计算出每一项的高度后,再把每一个的offsetop的值计算出来。
参考react-window的api使用,他也是把每一项的高度传递过去
所以我们顺着这个思路,去想办法处理这个高度
-
业务组件缓存datalist的每一项高度
-
虚拟列表组件根据缓存的高度计算缓存每个item的底部距离顶部top的距离
-
根据实时滚动后的scrolltop ,计算出datalis的startindex和endindex
-
根据计算的startindex和endindex,渲染item
和列表项等高的实现不同,这里不能传一个固定值 itemHeight,改为传入一个根据 index 获取列表项宽度函数 getItemHeight(index)。
虚拟列表组件会拿到每一项的高度,接下来就看我们需要在组件内缓存什么数据?
看一下之前我们是如何确定startindex和endindex,是根据scrolltop的高度可以覆盖多少个item来决定,所以其实我们需要的是每一项底部距离顶部的高度,这样对比scrolltop的距离,就可以确定我们要渲染的数据位置。
组件会根据这个函数,获取到每一个列表项的高度,来计算出我们的一个offsets数组,offsets数组存储的数据是每一个item的底部距离顶部的高度,这个数据的主要作用是滚动到一定的距离后,计算出我们需要展示的列表项有哪些。
假设几个列表项的高度数组 heights 为 [10, 20, 40, 100],那么 offsets 就是 [10, 30, 70, 170]。offsets的数据公式是:offsets[i] = offsets[i-1] + heights[i]
首先看下VariableSizeList 组件计算offsets的代码,就是上面的公式
getItemHeight 函数是外层传入获取item高度的函数,给个保底高低40,
heightsRef 是存储每一个item高度的的ref
后续的item加载的高度是未知的,加载新的item后我们需要重新计算高度来重置缓存的高度
虚拟列表组件通过 ref 提供了一个 resetHeight 方法来重置缓存的高度。
计算出 offsets 数组后,我们就可以计算需要渲染的列表项的起始(startIdx)和结束(endIdx)位置了。
因为 offsets 是有序数组,我们需要用 高效的二分查找 去查找,时间复杂度为 O(log n)。
查找startIdx 和endIdx 的位置,同时根据offsets计算出当前的外层scroll容器的高度,滚动条高度
最终渲染dom
上面的代码都在demo地址里面
小结
- 滑动比较快的时候,图片的加载可能跟不上,所以有图片的时候会看着留白明显些
- 图片加载比较耗时,图片加载完会对高度有变化,所以尽可能固定或者提前预设一个高度