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;