Back

React 无限滚动的完整指南

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