react18中实现加载百万条数据的滚动组件实现

虚拟列表技术

通过只渲染当前可见区域的数据来提高性能,而不是一次性渲染所有数据。这样可以显著减少 DOM 元素的数量,从而提高页面的加载速度和滚动流畅性。
核心思想

  • 初始化容器和数据:创建固定高度的容器,准备数据源。
  • 计算可视区域:获取容器高度,计算每个项的高度和可视区域的数据条数。
  • 渲染初始可见区域:计算起始和结束索引,渲染初始数据。
  • 监听滚动事件:绑定滚动事件,计算新的起始和结束索引,更新渲染数据。
  • 调整样式:计算偏移量,处理实际列表跟随父容器一起移动的情况

实现效果

请添加图片描述

react框架展示虚拟列表实现步骤:

从上面的效果图看出,全程只创建了固定的8个div,无论你又多少条数据,就只产生八个div,滚动的时候,产生了滚动视觉效果,只是数据内容更新了。

  • VirtualList组件
import {
    
     useRef, useState, useMemo, useEffect } from "react";
import "./index.css";

function VirtualList({
     
      listData, itemSize }) {
    
    
  const listRef = useRef(null);
  const [storeState, setStoreState] = useState({
    
    
    screenHeight: 0, // 可视区域的高度
    startOffset: 0, // 偏移量
    start: 0, // 起始数据下标
    end: 0, // 结束数据下标
  });

  const visibleData = useMemo(() => {
    
    
    const {
    
     start, end } = storeState;
    return listData.slice(start, Math.min(listData.length, end));
  }, [storeState]);
 
 // 滚动的时候来更新起始数据
  const scrollEvent = (e) => {
    
    
    const scrollTop = e.target.scrollTop;
    storeState.start = Math.floor(scrollTop / itemSize);
    storeState.end = storeState.start + visibleCount;
    storeState.startOffset = scrollTop - (scrollTop % itemSize);
    setStoreState({
    
     ...storeState });
  };

 // 一直在更新的高度
  const getTransform = useMemo(() => {
    
    
    return `translate3d(0, ${
      
      storeState.startOffset}px, 0)`;
  }, [storeState]);
  // 当前列表总高度
  const listHeight = useMemo(() => {
    
    
    return listData.length * itemSize;
  }, [listData]);

 // 能看到的数据
  const visibleCount = useMemo(() => {
    
    
    return storeState.screenHeight / itemSize;
  }, [storeState]);

  useEffect(() => {
    
    
    storeState.screenHeight = listRef.current.clientHeight;
    storeState.end = storeState.start + visibleCount;
    setStoreState({
    
     ...storeState });
  }, [visibleCount, setStoreState]);
  return (
    <>
      <div
        ref={
    
    listRef}
        className="infinite-list-container"
        onScroll={
    
    scrollEvent}
      >
        <div
          className="infinite-list-phantom"
          style={
    
    {
    
     height: listHeight + "px" }}
        ></div>

        <div className="infinite-list" style={
    
    {
    
     transform: getTransform }}>
          {
    
    visibleData.map((item, index, array) => {
    
    
            return (
              <div
                className="infinite-list-item"
                key={
    
    index}
                style={
    
    {
    
     height: itemSize + "px", lineHeight: itemSize + "px" }}
              >
                {
    
    item.name}
              </div>
            );
          })}
        </div>
      </div>
    </>
  );
}

VirtualList.defaultProps = {
    
     itemSize: 50, listData: [] };
export default VirtualList;
  • 组件的使用
import VirtualList from "./virtualList";

function Vlist() {
    
    
  const listData = Array.from({
    
     length: 1000 }, (_, index) => ({
    
    
    id: index,
    name: `item ${
      
      index}`,
    value: `value ${
      
      index}`,
  }));
  return (
    <div className="container-box">
      <VirtualList listData={
    
    listData} />
    </div>
  );
}

export default Vlist;
  • index.css文件
.container-box{
    
    
  height: 400px;
  width: 100%;
  background-color: aliceblue;
  border: #000 1px solid;
}


.infinite-list-container {
    
    
  height: 100%;
  overflow: auto;
  position: relative;
  -webkit-overflow-scrolling: touch; /* 启用触摸滚动 */
  
}
.infinite-list-phantom{
    
    
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  z-index: -1;    /* 置于背景层 **/
}
.infinite-list{
    
    
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  text-align: center;
}
.infinite-list-item {
    
    
  border-bottom: 1px solid #000;
  box-sizing: border-box;
}

猜你喜欢

转载自blog.csdn.net/qq_27702739/article/details/143502785