长列表展示优化方案-虚拟列表

366 阅读1分钟

在项目开发中,有时候遇到需要展示海量数据展示的场景,也就是长列表渲染

目前在做的项目中列表场景使用此方案处理了下

使用之前页面稍微上滑刷新几页基本内存就是翻倍的增长

对用户使用体验不佳

长列表渲染大量节点导致的性能问题

  1. 首次渲染的时候会一次性渲染大量的节点,占用大量的gpu资源,页面滚动操作等会有明显的卡顿。

  2. 即使我们使用一些分片加载或者上滑加载的方案优化,但是渲染出来的dom依然会占用大量内存,节点越多,性能影响越大。

虚拟列表,顾名思义,就是不实际的去展示这个列表,核心思路就是只展示用户实际这个节点应该看到的数据,其余的部分虚拟化,同时不影响用户对原列表操作(滑动,滚动)上的体验。

虚拟列表主要包含三个区域,虚拟区/缓冲区/展示区,

目前使用比较多的虚拟列表库是 react-window,

下图是它的dom展示形式。

参考这个我们自己去实现类似的功能,同时适配我们自己的场景(下拉加载,上滑刷新等)

高度固定的虚拟列表

每个item是固定的这种好处理些,简单说有三种实现方案

  1. 添加一个将 items 往下推到正确位置的空元素,动态计算推下去的距离

  2. 使用 transform 方案 

  3. 使用定位的方案,类似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 优化

动态列表高度

demo地址

这种和固定高度不同,不能再传递固定的高度了,需要把每一项的高度都计算出来,再去处理展示的逻辑

计算出每一项的高度后,再把每一个的offsetop的值计算出来。

参考react-window的api使用,他也是把每一项的高度传递过去

所以我们顺着这个思路,去想办法处理这个高度

  1. 业务组件缓存datalist的每一项高度

  2. 虚拟列表组件根据缓存的高度计算缓存每个item的底部距离顶部top的距离

  3. 根据实时滚动后的scrolltop ,计算出datalis的startindex和endindex

  4. 根据计算的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地址里面

小结

  1. 滑动比较快的时候,图片的加载可能跟不上,所以有图片的时候会看着留白明显些
  2. 图片加载比较耗时,图片加载完会对高度有变化,所以尽可能固定或者提前预设一个高度

相关阅读

长列表优化:用 React 实现虚拟列表