React大数据量展示Table优化(虚拟列表)

什么是虚拟列表

虚拟列表只对可视区域数据进行渲染,对非可视区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。

当服务端返回前端几万甚至十几万条记录需要前端展示,我们屏幕的可视区域高度为800px,列表项高度为40px,此时我们在屏幕中最多只能看到20个列表项,那么在渲染的时候,我们只需加载20条即可。

实现思路

虚拟列表实际上只对可视区域的数据项进行渲染

  • 可视区域(visibleHeight): 根据屏幕可视区域动态计算或自定义固定高度
  • 数据渲染项(visibleCount):可视区域除以行高并向下取整
  • startIndex: 初始为0,听过滚动条偏移计算
  • endIndex: startIndex + visibleCount; 数据结束位置索引,可额外预加载几条数据

virtual.png

监听滚动条滚动事件,根据偏移距离计算startIndex

virtual2.png

监听逻辑实现:

  useEffect(() => {
    const onScrollChange = (e: React.WheelEvent) => {
      const top = (e.target as HTMLElement).scrollTop
      const index = Math.floor(top / rowHeight)
      setScrollTop(top)
      setStartIndex(index ? index + 1 : 0)
    }
    virtualizedRef.current.addEventListener('scroll', onScrollChange)
    return () => {
      if (virtualizedRef.current) {
        virtualizedRef.current.removeEventListener('scroll', onScrollChange)
      }
    }
  }, [])
复制代码

HTML结构如下:

  • virtualized_placeholder: 容器内占位,高度为列表总高度,撑满父容器,用于可视区域形成滚动条
    <div ref={virtualizedRef} style={{ height: visibleHeight }}>
      <table style={{ transform: `translate3d(0px, ${scrollTop}px, 0)` }}>
        <thead>{...}</thead>
        <tbody>{...}</tbody>
      </table>
      <div className="virtualized_placeholder" style={{ height: placeHeight }} />
     </div>
复制代码

主要逻辑:

  • 设置容器占位高度,计算可视区域数据项

  • 监听容器滚动事件,计算偏移距离,startIndex,组件卸载移除滚动事件

  • startIndex作为deps依赖项,当发生改变更新展示数据

  useEffect(() => {
    const placeH = ((dataSource.length) * rowHeight) + rowHeight
    setPlaceHeight(placeH)
    setVisibleCount(Math.floor(visibleHeight / rowHeight) + 2)
  }, [dataSource, rowHeight])

  useEffect(() => {
    const onScrollChange = (e: React.WheelEvent) => {
      const top = (e.target as HTMLElement).scrollTop
      const index = Math.floor(top / rowHeight)
      setScrollTop(top)
      setStartIndex(index ? index + 1 : 0)
    }
    virtualizedRef.current.addEventListener('scroll', onScrollChange)
    return () => {
      if (virtualizedRef.current) {
        virtualizedRef.current.removeEventListener('scroll', onScrollChange)
      }
    }
  }, [])

  useEffect(() => {
    const data = dataSource.slice(startIndex, startIndex + visibleCount)
    setShowData(data)
  }, [startIndex, visibleCount, dataSource])
复制代码

结果对比

加载3000条数据普通Table与虚拟列表结果对比(单位:ms)

通过Chrome的Performance工具来详细分析性能瓶颈

  • Recalculate Style:样式计算,浏览器根据css选择器计算哪些元素应该应用哪些规则,确定每个元素具体的样式

  • Layout:布局,知道元素应用哪些规则之后,浏览器开始计算它要占据的空间大小及其在屏幕的位置

B9D2E56B-09E5-4de8-BE13-1AE99976B751.png

参考

「前端进阶」高性能渲染十万条数据(虚拟列表)

完整代码地址

github.com/hanyueqiang…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享