
本文手把手教你使用原生 react(不依赖第三方库)实现无限滚动:监听滚动到底部、分页加载 json 数据、状态管理与防重复触发,附完整可运行代码与关键注意事项。
在 React 中实现真正的无限滚动,核心在于监听用户滚动行为 → 判断是否触达容器底部 → 触发下一页数据加载 → 合并渲染。下面我们将从零构建一个健壮、可复用的无限滚动组件,完全基于 React 内置 Hook(useState、useEffect、useCallback、useRef),不引入任何外部库。
✅ 基础结构与数据准备
假设你已有一个静态 JSON 数组 data(例如来自 API 或本地 mock):
const data = [
{ _uid: '1', name: 'Conto A' },
{ _uid: '2', name: 'Conto B' },
// ... 共 50+ 条
];我们按每页 5 条分页加载(可根据需求调整),初始只渲染第一页,后续滚动到底部时自动追加下一页。
✅ 核心实现:滚动监听 + 分页加载
关键点在于精准判断“滚动到底部”——不能仅靠 window.scrollY + window.innerHeight >= document.body.scrollHeight,因为该方式在单页应用中易受布局变化干扰。更可靠的做法是监听目标容器(如列表父元素)的 scroll 事件,并结合 ref 获取其滚动位置:
import React, { useState, useEffect, useCallback, useRef } from 'react';
export default function InfiniteScrollList({ initialData = [], itemsPerPage = 5 }) {
const [limiteData, setLimiteData] = useState([]);
const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(false);
const [hasMore, setHasMore] = useState(true); // 防止无数据时持续加载
const containerRef = useRef(null);
// 模拟异步数据获取(替换为真实 fetch 时,请改为 Promise)
const fetchData = useCallback(() => {
if (isLoading || !hasMore) return;
setIsLoading(true);
setTimeout(() => {
const start = (page - 1) * itemsPerPage;
const end = start + itemsPerPage;
const newData = initialData.slice(start, end);
if (newData.length === 0) {
setHasMore(false);
} else {
setLimiteData(prev => [...prev, ...newData]);
}
setPage(prev => prev + 1);
setIsLoading(false);
}, 800);
}, [page, isLoading, hasMore, initialData, itemsPerPage]);
// 监听容器滚动,判断是否触底
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleScroll = () => {
if (
container.scrollTop + container.clientHeight >= container.scrollHeight - 10 &&
!isLoading &&
hasMore
) {
fetchData();
}
};
container.addEventListener('scroll', handleScroll);
return () => container.removeEventListener('scroll', handleScroll);
}, [fetchData, isLoading, hasMore]);
// 首次加载第一页
useEffect(() => {
fetchData();
}, [fetchData]);
return (
{limiteData.map((blok) => (
{blok.name}
ID: {blok._uid}
))}
{isLoading && (
Loading more...
)}
{!hasMore && limiteData.length > 0 && (
You've reached the end.
)}
);
}? 为什么用容器滚动而非 window? 在现代 React 应用(尤其嵌套路由或复杂布局)中,window 可能并非滚动主体。显式绑定到内容容器(如 )更可控、兼容性更好。
✅ 替换你的原始渲染逻辑(适配 Suspense 和 Conto 组件)
你原有的 data?.map(...) 渲染逻辑可无缝迁移到 limiteData 上。注意:Suspense 应包裹异步组件本身(如 Conto),而非整个加载状态。优化后的片段如下:
{limiteData.map((conto) => (
Loading...









