React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > antd 3.x Table组件虚拟列表

antd 3.x Table组件如何快速实现虚拟列表详析

作者:Yue栎廷

这篇文章主要给大家介绍了关于antd 3.x Table组件如何快速实现虚拟列表的相关资料,文中通过实例代码介绍的非常详细,对大家学习或者使用antd具有一定的参考学习价值,需要的朋友可以参考下

1. 前言

随着互联网的发展,web展示的内容越来越丰富,也越来越无穷。我们在实际开发中难免会遇到长列表数据渲染,而又不适合分页的业务场景,如果浏览器直接渲染海量数据,会造成页面卡死,严重时导致浏览器资源耗尽,直接崩溃掉。这种情况用户与产品是无法接受的,浏览器性能与业务需求产生了对立,因此虚拟列表技术被提出,为这种尴尬的场面提供了一线生机。

2. 虚拟列表

虚拟列表其实是按需显示的一种实现,即只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。假设有10万条记录需要同时渲染,我们屏幕的可见区域的高度为500px,而列表项的高度为50px,则此时我们在屏幕中最多只能看到10个列表项,那么在渲染的时候,我们只需加载可视区的那10条即可,触发页面滚动时,实时替换当前应该展示在页面中的10条数据。

它的名词解释由一张图来诠释,如下:

触发滚动后,可视区域内的数据变化:

首先,准备dom结构:

<div className="wrapper" style={{
    position: "relative",
    overflow: "auto"
}}>
    <div className="placeholder-list" style={{ 
        height: `${visibleHeight}px` 
    }}></div>
    <div className="render-list" style={{ 
        postion: "absolute", 
        top: 0, 
        left: 0
    }}>...</div>
</div>

wrapper添加滚动事件实现逻辑:

scrollEvent(e){
    const startIdx = Math.floor(e.target.scrollTop / itemHeight);
    const endIdx = startIdx + visibleCount;
    setList(source.slice(startIdx, endIdx));
    // 设置偏移距离,保持数据在视图中
    const offset = startIdx * itemHeight;
    listRef.current.style.top = offset + "px";
}

我们发现,快速滚动时最下方会出现空白的现象,因为此时数据还没渲染成功。为了优化此空白,考虑多渲染2条数据作为缓冲区。因此visibelCount=Math.ceil(visibelHeight / itemHeight) + 2

代码完整示例:

import { useCallback, useEffect, useRef, useState } from "react";

const visibleHeight = 360;
const itemHeight = 50;
const visibleCount = Math.ceil(visibleHeight / itemHeight) + 2;
const totalCount = 100;

const source = Array.from(Array(totalCount), (item, index) => index);

export default function VirtualList() {
  const [list, setList] = useState(source);
  const listRef = useRef();

  const scrollEvent = useCallback((e) => {
    const startIdx = Math.floor(e.target.scrollTop / itemHeight);
    const endIdx = startIdx + visibleCount;
    setList(source.slice(startIdx, endIdx));
    const offset = startIdx * itemHeight;
    listRef.current.style.top = offset + "px";
  }, []);
  
  useEffect(() => {
    listRef.current = document.querySelector(".list");
  }, []);
  
  return (
    <div
      style={{
        backgroundColor: "#FFF",
        height: visibleHeight + 'px',
        textAlign: "center",
        overflow: "auto",
        position: "relative",
        overscrollBehavior: 'contain'
      }}
      onScroll={scrollEvent}
    >
      <div style={{ height: totalCount * itemHeight + 'px' }}></div>
      <div
        className="list"
        style={{
          position: "absolute",
          top: 0,
          left: 0,
          width: "100%",
          height: visibleHeight + 'px'
        }}
      >
        {list.map((item) => {
          return (
            <div
              key={item}
              style={{ height: itemHeight + 'px', borderBottom: "1px solid #eee" }}
            >
              {item}
            </div>
          );
        })}
      </div>
    </div>
  );
}

3. 虚拟table

终于来到了标题的内容,如何对antd table3.x进行虚拟表格的封装。其实和上述的代码差不多,只不过对于有的新手同学来讲,可能有点摸不着入口,所以有了本节内容。

目前在antd4.x版本table已经实现了开启虚拟列表的配置,拿来即用。针对3.x的版本自己实现了一个虚拟table,解决了业务上长列表渲染卡顿的问题。

注意:Table每项需要定高,因此columns属性中需要ellipsis:true保证数据只展示一行,溢出展示省略号。

根据上节内容介绍的虚拟列表思路,我们需要准备2个容器,一个内容容器,Table已经提供了,另一个占位容器没有提供,所以需要手动创建一个并放在合适的地方。通过开发者工具审查元素找到Table内提供的那个内容容器.ant-table-body table,获取其dom。父容器.ant-table-body,创建一个占位容器div,追加到父容器内。通过元素审查也知道Table tr高度为54px,即itemHeight=54

知道了类名,就可以获取到Table的dom为所欲为了。

useEffect(() => {
    const parentNode = document.querySelector('.ant-table-body');
    const table = document.querySelector('.ant-table-body table');
    // 用ref保持table方便在滚动事件中使用table dom
    tableRef.current = table;
    // 创建一个占位的div,高度等于所有数据高度,用来撑开容器展示滚动条
    const placeholderWrapper = document.createElement('div');
    placeholderWrapper.style.height = itemHeight * totalCount + 'px'
    parentNode.appendChild(placeholderWrapper);
    // 子绝父相口诀,为table设置定位,脱离文档流,把位置让给占位盒子
    parentNode.style.position = 'relative';
    table.style.position = 'absolute';
    table.style.top = 0;
    table.style.left = 0;
    // 添加滚动事件
    parentNode.addEventListener('scroll', scrollEvent)
    return () => {
      // 清理占位盒子
      parentNode.removeChild(placeholderWrapper);
      parentNode.removeEventListener('scroll', scrollEvent)
    }
  }, [scrollEvent]);

接下来实现滚动事件,和上节内容一致,保存范围索引到state中:

const scrollEvent = useCallback((e) => {
    const startIdx = Math.floor(e.target.scrollTop / itemHeight);
    const endIdx = startIdx + visibleCount;
    // 保存当前的范围索引,用来slice源数据给展示用
    setRange([startIdx, endIdx]);
    const offset = startIdx * itemHeight;
    tableRef.current.style.top = offset + "px";
  }, []);

根据范围索引,截取当前要展示的数据项

const [range, setRange] = useState([]);
// 这个renderList就是需要给Table组件的
const renderList = useMemo(() => {
    const [start, end] = range;
    return dataSource.slice(start, end)
  }, [range])

return <Table dataSource={renderList} />

全文示例代码Github地址

4.总结

本文只是实现了在固定每项列表高度的情况下的虚拟列表,现实很多情况是不定高的。这个比定高的复杂,不过原理也是一样的,多了一步需要计算渲染后的实际高度的步骤。后续会完善不定高的虚拟列表的实现。

本文的内容也是我在工作中遇到的情况,应该很多其他小伙伴也会遇到antd 3.x table的虚拟化的问题,希望能给小伙伴们一点思路。因此有了本文,也是自己一次关于输入与输出的记录与沉淀。

到此这篇关于antd 3.x Table组件如何快速实现虚拟列表的文章就介绍到这了,更多相关antd 3.x Table组件虚拟列表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文