12k
All articles

React 无限滚动的完整指南

介绍如何在 React 中通过第三方包或自定义 IntersectionObserver Hook 实现无限滚动,并有效处理性能优化、加载状态及边界情况。

OpenReplay Team
OpenReplay Team
React 无限滚动的完整指南

无限滚动是一种用户体验模式,当用户向下滚动页面时加载新内容。这种模式常见于社交媒体信息流、时间线和新闻应用中,它是一种流畅浏览大型数据集的方式,无需使用分页按钮。在本指南中,你将学习如何使用第三方库和自定义钩子在 React 中实现无限滚动。

要点

  • 学习在 React 中实现无限滚动的 2 种实用方法
  • 使用带有 IntersectionObserver 的自定义钩子或第三方包
  • 处理加载状态、分页和空结果

什么是无限滚动?

无限滚动在用户滚动到列表末尾附近时加载新数据。它消除了分页的需求,创造了连续的浏览体验。你可以在 Instagram、Twitter 和 Reddit 等产品中看到这种模式。

方法一:使用第三方包(react-infinite-scroll-component

在 React 中实现无限滚动的最简单方法是使用经过充分测试的包。

第一步:安装

npm install react-infinite-scroll-component

第二步:基本使用示例

import InfiniteScroll from 'react-infinite-scroll-component';

function Feed() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(true);

  const fetchMoreData = async () => {
    const res = await fetch(`/api/posts?page=${page}`);
    const newItems = await res.json();
    if (newItems.length === 0) setHasMore(false);
    else {
      setItems(prev => [...prev, ...newItems]);
      setPage(prev => prev + 1);
    }
  };

  return (
    <InfiniteScroll
      dataLength={items.length}
      next={fetchMoreData}
      hasMore={hasMore}
      loader={<h4>Loading...</h4>}
      endMessage={<p>No more results</p>}
    >
      {items.map(item => <div key={item.id}>{item.title}</div>)}
    </InfiniteScroll>
  );
}

优点

  • 快速设置
  • 内置加载和结束状态

缺点

  • 对滚动逻辑的控制较少
  • 增加了包依赖

方法二:使用 IntersectionObserver 的自定义钩子

这种方法给你完全的控制权,且无额外依赖。

第一步:创建钩子

function useInfiniteScroll(callback, ref) {
  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) callback();
    });
    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, [callback, ref]);
}

第二步:在组件中使用

function Feed() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(true);
  const sentinelRef = useRef(null);

  const loadMore = async () => {
    if (!hasMore) return;
    const res = await fetch(`/api/posts?page=${page}`);
    const newItems = await res.json();
    if (newItems.length === 0) setHasMore(false);
    else {
      setItems(prev => [...prev, ...newItems]);
      setPage(prev => prev + 1);
    }
  };

  useInfiniteScroll(loadMore, sentinelRef);

  return (
    <div>
      {items.map(item => <div key={item.id}>{item.title}</div>)}
      <div ref={sentinelRef} style={{ height: 1 }} />
    </div>
  );
}

优点

  • 完全可定制
  • 适用于任何滚动逻辑或 API

缺点

  • 需要更多设置
  • IntersectionObserver 在 IE11 中不受支持

实际应用模式和提示

  • 防抖 API 请求以避免过度加载
  • 显示加载动画或骨架屏以提升用户体验
  • 添加重试逻辑处理网络错误
  • 处理边缘情况如零结果或空页面
if (items.length === 0 && !hasMore) {
  return <p>No posts found.</p>;
}

性能考虑

  • 渲染成千上万个项目时使用 react-window 等库进行虚拟化
  • 记忆化项目组件以防止重新渲染
  • 正确清理观察者以避免内存泄漏
useEffect(() => {
  const observer = new IntersectionObserver(...);
  return () => observer.disconnect();
}, []);

结论

无限滚动是现代 UI 中的常见功能。无论你偏好第三方包的简便性还是自定义钩子的控制性,React 都使这两种方法变得易于实现。在实现无限滚动时,始终考虑性能和加载反馈。

常见问题

在 React 中实现无限滚动的最简单方法是什么?

使用 `react-infinite-scroll-component` 等库可以提供快速便捷的设置。

当没有更多数据时如何停止加载?

当 API 返回空数组时,将 `hasMore` 标志设置为 false。

无限滚动会影响性能吗?

如果渲染太多项目,确实会影响性能。使用 `react-window` 等虚拟化工具来管理大型列表。

如何仅在接近底部时添加无限滚动?

使用 `IntersectionObserver` 在底部的 `div`(哨兵元素)进入视图时触发数据加载。

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers

We use cookies to improve your experience. By using our site, you accept cookies.