ByteNoteByteNote

字节笔记本

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 组件配置

属性类型说明
dataLengthnumber当前数据长度,用于触发加载检测
nextfunction加载更多数据的回调函数
hasMoreboolean是否还有更多数据
loaderReactNode加载中显示的组件
endMessageReactNode数据加载完成后显示的提示

工作原理

  1. 滚动监听: InfiniteScroll 组件内部监听滚动事件
  2. 距离计算: 当滚动距离底部小于阈值时触发加载
  3. 防抖处理: 避免重复触发加载请求
  4. 状态更新: 追加新数据后触发组件重新渲染

使用示例

自定义加载提示

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';

最佳实践

  1. 合理设置每页加载数量(10-20 条)
  2. 添加加载失败的错误处理
  3. 检测数据是否加载完成,及时设置 hasMore = false
  4. 考虑添加"返回顶部"按钮
  5. 长列表场景结合虚拟滚动优化性能

相关资源

分享: