and滚动下拉加载

这段代码实现了一个基于 ‌Intersection Observer API‌ 的无限滚动列表组件,结合 React Hooks 和 Ant Design 组件完成数据分页加载。以下是核心逻辑解析:

一、关键功能模块分解

1. 状态管理
const [data, setData] = useState([]);        // 已加载的数据列表
const [loading, setLoading] = useState(false);// 加载中状态
const [page, setPage] = useState(1);          // 当前页码
const [hasMore, setHasMore] = useState(true); // 是否还有更多数据可加载
const containerRef = useRef(null);            // 滚动监听触发的 DOM 元素
2. 数据加载逻辑 (loadData)
const loadData = async () => {
  if (!hasMore || loading) return; // 防御性判断:无更多数据或正在加载时阻止请求
  
  setLoading(true);
  try {
    const params = { 
      current: page, 
      size: PAGE_SIZE,
      // ...其他固定查询参数
    };
    
    const response = await getAllRecords(params); // 发起 API 请求
    const newData = response.data.records || [];
    const total = response.data.total || 0;

    // 判断是否已加载完所有数据
    if (newData.length === 0 || data.length + newData.length >= total) {
      setHasMore(false);
    }

    // 合并新旧数据(函数式更新避免闭包问题)
    setData(prevData => [...prevData, ...newData]);
  } catch (error) {
    console.error("加载失败:", error);
  } finally {
    setLoading(false); // 无论成功失败都关闭加载状态
  }
};
3. 滚动监听机制 (useEffect + Intersection Observer)
useEffect(() => {
  const observer = new IntersectionObserver(([entry]) => {
    // 当触发元素进入视口 + 有更多数据 + 非加载状态时,触发翻页
    if (entry.isIntersecting && hasMore && !loading) {
      setPage(prev => prev + 1); // 页码递增会触发后续的 loadData
    }
  }, { threshold: 1.0 }); // 完全进入视口才触发

  if (containerRef.current) observer.observe(containerRef.current);
  
  return () => observer.disconnect(); // 组件卸载时解除监听
}, [hasMore, loading]); // 依赖项确保状态同步

下面是代码  可以复制一下  这边是react的版本的

import React, { useState, useEffect, useRef } from "react";
import { List, Spin } from "antd";
import { getAllRecords } from "@/services/clockinLog";

const PAGE_SIZE = 20; // 每页加载的数据量


const InfiniteScrollList = () => {
    const [data, setData] = useState([]); // 存储数据
    const [loading, setLoading] = useState(false); // 控制加载状态
    const [page, setPage] = useState(1); // 记录当前页数
    const [hasMore, setHasMore] = useState(true); // 控制是否还有更多数据
    const containerRef = useRef(null); // 监听滚动加载的 DOM

    // **监听 page 变化,触发数据加载**
    useEffect(() => {
        loadData();
    }, [page]); // **监听 page 变化,而不是只在组件挂载时执行**

    useEffect(() => {
        const observer = new IntersectionObserver(([entry]) => {
            if (entry.isIntersecting && hasMore && !loading) {
                setPage(prevPage => prevPage + 1); // **让 `page` 递增,而不是直接调用 `loadData`**
            }
        }, { root: null, rootMargin: "0px", threshold: 1.0 });

        if (containerRef.current) {
            observer.observe(containerRef.current);
        }

        return () => observer.disconnect();
    }, [hasMore, loading]);

    const loadData = async () => {
        if (!hasMore || loading) return; // 避免重复请求
        setLoading(true);

        try {
            const params = {
                current: page, // 传递当前页
                size: PAGE_SIZE,
                clubIdStr: "1827966716816719874,1764481893494001665,1764481818038472705,1762383449803558914,1749705502700343297,1737040398271426562,1737040285528535041,1881224427603558402,1858474790089990146",
                startDate: "2025-01-02 00:00:00",
                endDate: "2025-04-22 23:59:59",
                alarmStatus: 1,
            };

            const response = await getAllRecords(params);
            console.log('getAllRecords:', response);

            const newData = response.data.records || []; // 避免空数据错误
            const total = response.data.total || 0;

            // **如果返回数据为空,说明已经到底了**
            if (newData.length === 0 || data.length + newData.length >= total) {
                setHasMore(false);
            }

            // **使用回调方式更新数据,确保数据不会重复**
            setData(prevData => [...prevData, ...newData]);
        } catch (error) {
            console.error("加载数据失败:", error);
        } finally {
            setLoading(false);
        }
    };

    return (
        <div style={{ height: "400px", overflow: "auto", border: "1px solid #ddd", padding: "10px" }}>
            <List
                dataSource={data}
                renderItem={(item, index) => <List.Item key={index}>{index}{item.detailText}</List.Item>}
            />
            <div ref={containerRef} style={{ textAlign: "center", padding: "10px" }}>
                {loading ? <Spin /> : hasMore ? "滚动加载中..." : "没有更多数据了"}
            </div>
        </div>
    );
};

export default InfiniteScrollList;

 内嵌入的滚动 上方的方法并不适用 可以使用下面的办法 我这边是利用了Popover 里面的 content

可以建议使用下面的办法 

 

import React, { useState, useEffect, useRef } from "react";
import { Popover, List, Spin, Button } from "antd";
import { getAllRecords } from "@/services/clockinLog";

const PAGE_SIZE = 20; // 每页加载的数据量

const InfiniteScrollPopover = () => {
    const [data, setData] = useState([]); // 存储数据
    const [loading, setLoading] = useState(false); // 控制加载状态
    const [page, setPage] = useState(1); // 记录当前页数
    const [hasMore, setHasMore] = useState(true); // 控制是否还有更多数据
    const containerRef = useRef(null); // 监听滚动加载的 DOM

    // **监听 page 变化,触发数据加载**
    useEffect(() => {
        loadData();
    }, [page]); // **监听 page 变化,而不是只在组件挂载时执行**

    useEffect(() => {
        const observer = new IntersectionObserver(([entry]) => {
            if (entry.isIntersecting && hasMore && !loading) {
                setPage(prevPage => prevPage + 1); // **让 `page` 递增,而不是直接调用 `loadData`**
            }
        }, { root: null, rootMargin: "0px", threshold: 1.0 });

        if (containerRef.current) {
            observer.observe(containerRef.current);
        }

        return () => observer.disconnect();
    }, [hasMore, loading]);

    const loadData = async () => {
        if (!hasMore || loading) return; // 避免重复请求
        setLoading(true);

        try {
            const params = {
                current: page, // 传递当前页
                size: PAGE_SIZE,
                clubIdStr: "1827966716816719874,1764481893494001665,1764481818038472705,1762383449803558914,1749705502700343297,1737040398271426562,1737040285528535041,1881224427603558402,1858474790089990146",
                startDate: "2025-01-02 00:00:00",
                endDate: "2025-04-22 23:59:59",
                alarmStatus: 1,
            };

            const response = await getAllRecords(params);
            console.log('getAllRecords:', response);

            const newData = response.data.records || []; // 避免空数据错误
            const total = response.data.total || 0;

            // **如果返回数据为空,说明已经到底了**
            if (newData.length === 0 || data.length + newData.length >= total) {
                setHasMore(false);
            }

            // **使用回调方式更新数据,确保数据不会重复**
            setData(prevData => [...prevData, ...newData]);
        } catch (error) {
            console.error("加载数据失败:", error);
        } finally {
            setLoading(false);
        }
    };

    // 定义 Popover 的内容
    const content = (
        <div style={{ width: "300px", maxHeight: "400px", overflow: "auto", border: "1px solid #ddd", padding: "10px" }}>
            <List
                dataSource={data}
                renderItem={(item, index) => <List.Item key={index}>{index} {item.detailText}</List.Item>}
            />
            <div ref={containerRef} style={{ textAlign: "center", padding: "10px" }}>
                {loading ? <Spin /> : hasMore ? "滚动加载中..." : "没有更多数据了"}
            </div>
        </div>
    );
    const handleScroll = (e) => {
        const { scrollTop, clientHeight, scrollHeight } = e.target;
        if (scrollTop + clientHeight >= scrollHeight - 10 && hasMore && !loading) {
            setPage(prevPage => prevPage + 1);
        }
    };

    return (
        <Popover
            title="数据列表"
            trigger="click"
            content={
                <div
                    style={{ width: "300px", maxHeight: "400px", overflowY: "auto" }}
                    onScroll={handleScroll} // 监听滚动
                >
                    <List
                        dataSource={data}
                        renderItem={(item, index) => <List.Item key={index}>{index} {item.detailText}</List.Item>}
                    />
                    <div style={{ textAlign: "center", padding: "10px" }}>
                        {loading ? <Spin /> : hasMore ? "滚动加载中..." : "没有更多数据了"}
                    </div>
                </div>
            }
        >
            <Button type="primary">查看数据</Button>
        </Popover>

    );
};

export default InfiniteScrollPopover;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_2524963996

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值