字
字节笔记本
2026年5月3日
React 无限滚动组件 (InfiniteScroll)
API中转
¥120
本笔记记录了使用 react-infinite-scroll-component 库实现无限滚动列表的完整示例。无限滚动是一种常见的用户体验优化技术,当用户滚动到页面底部时自动加载更多内容,避免了传统分页的跳转。
核心实现
完整代码
jsx
import React, { useState } from "react";
import InfiniteScroll from "react-infinite-scroll-component";
const Content = ({ data }) => {
const [posts, setPosts] = useState(data);
const [hasMore, setHasMore] = useState(true);
const getMorePost = async () => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos?_start=${posts.length}&_limit=10`
);
const newPosts = await res.json();
setPosts((post) => [...post, ...newPosts]);
};
return (
<>
<InfiniteScroll
dataLength={posts.length}
next={getMorePost}
hasMore={hasMore}
loader={<h3> Loading...</h3>}
endMessage={<h4>Nothing more to show</h4>}
>
{posts.map((data) => (
<div key={data.id}>
<div className="back">
<strong> {data.id}</strong> {data.title}
</div>
{data.completed}
</div>
))}
</InfiniteScroll>
<style jsx>
{`
.back {
padding: 10px;
background-color: dodgerblue;
color: white;
margin: 10px;
}
`}
</style>
</>
);
};
export default Content;关键要点
1. 状态管理
jsx
const [posts, setPosts] = useState(data); // 存储所有已加载的数据
const [hasMore, setHasMore] = useState(true); // 是否还有更多数据可加载posts: 维护当前已加载的所有数据列表hasMore: 控制是否继续触发加载(到达数据末尾时设为false)
2. 数据加载函数
jsx
const getMorePost = async () => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos?_start=${posts.length}&_limit=10`
);
const newPosts = await res.json();
setPosts((post) => [...post, ...newPosts]);
};关键点:
- 使用
posts.length作为分页起始位置 (_start参数) - 每次加载 10 条数据 (
_limit=10) - 使用展开运算符
[...post, ...newPosts]追加新数据 - 使用函数式
setState确保状态更新的准确性
3. InfiniteScroll 组件配置
| 属性 | 类型 | 说明 |
|---|---|---|
dataLength | number | 当前数据长度,用于触发加载检测 |
next | function | 加载更多数据的回调函数 |
hasMore | boolean | 是否还有更多数据 |
loader | ReactNode | 加载中显示的组件 |
endMessage | ReactNode | 数据加载完成后显示的提示 |
工作原理
- 滚动监听:
InfiniteScroll组件内部监听滚动事件 - 距离计算: 当滚动距离底部小于阈值时触发加载
- 防抖处理: 避免重复触发加载请求
- 状态更新: 追加新数据后触发组件重新渲染
使用示例
自定义加载提示
jsx
<InfiniteScroll
dataLength={posts.length}
next={getMorePost}
hasMore={hasMore}
loader={<div className="loading-spinner"><span>加载中...</span></div>}
endMessage={<p style={{ textAlign: 'center' }}><b>已经到底了!</b></p>}
>
{/* 列表内容 */}
</InfiniteScroll>添加错误处理
jsx
const getMorePost = async () => {
try {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos?_start=${posts.length}&_limit=10`
);
if (!res.ok) throw new Error('请求失败');
const newPosts = await res.json();
if (newPosts.length === 0) {
setHasMore(false);
return;
}
setPosts((post) => [...post, ...newPosts]);
} catch (error) {
console.error('加载失败:', error);
setHasMore(false);
}
};性能优化
1. 虚拟化长列表
当列表项非常多时,考虑使用虚拟滚动:
jsx
import { FixedSizeList } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';2. 防抖加载
jsx
import { debounce } from 'lodash';
const getMorePost = debounce(async () => {
// 加载逻辑
}, 300);3. 数据缓存
jsx
const [cache, setCache] = useState({});
const getMorePost = async () => {
const cacheKey = `page_${Math.floor(posts.length / 10)}`;
if (cache[cacheKey]) {
setPosts((post) => [...post, ...cache[cacheKey]]);
return;
}
// 请求新数据并缓存
};常见问题
问题 1: 重复加载数据
原因: 滚动触发过快,导致多次调用 next 函数
解决方案:
jsx
const [isLoading, setIsLoading] = useState(false);
const getMorePost = async () => {
if (isLoading) return;
setIsLoading(true);
try {
// 加载逻辑
} finally {
setIsLoading(false);
}
};问题 2: hasMore 状态未更新
原因: 后端返回空数组但未设置 hasMore = false
解决方案:
jsx
const newPosts = await res.json();
if (newPosts.length === 0) {
setHasMore(false);
return;
}问题 3: 样式冲突
解决方案:
jsx
<InfiniteScroll
style={{ overflow: 'visible' }} // 覆盖默认样式
>安装依赖
bash
npm install react-infinite-scroll-component
# 或
yarn add react-infinite-scroll-component
# 或
pnpm add react-infinite-scroll-component适用场景
| 场景 | 适用性 | 说明 |
|---|---|---|
| 社交媒体动态 | ✅ 推荐 | 微博、Twitter 风格的时间流 |
| 商品列表 | ✅ 推荐 | 电商网站的商品展示 |
| 搜索结果 | ⚠️ 慎用 | 需要明确分页时不适用 |
| 数据量巨大 | ⚠️ 需优化 | 结合虚拟滚动使用 |
| 表格数据 | ❌ 不推荐 | 传统分页更合适 |
替代方案
1. 自定义 Intersection Observer
jsx
import { useEffect, useRef } from 'react';
function useInfiniteScroll(callback) {
const observerRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) callback();
},
{ threshold: 1.0 }
);
if (observerRef.current) observer.observe(observerRef.current);
return () => observer.disconnect();
}, [callback]);
return observerRef;
}2. TanStack Virtual (React Query + 虚拟滚动)
jsx
import { useInfiniteQuery } from '@tanstack/react-query';
import { useVirtualizer } from '@tanstack/react-virtual';最佳实践
- 合理设置每页加载数量(10-20 条)
- 添加加载失败的错误处理
- 检测数据是否加载完成,及时设置
hasMore = false - 考虑添加"返回顶部"按钮
- 长列表场景结合虚拟滚动优化性能
相关资源
分享: