信息发布→ 登录 注册 退出

如何从零实现 React 中的无限滚动(Infinite Scroll)功能

发布时间:2025-12-29

点击量:

本文手把手教你使用原生 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) => (
  
    
  
))}

✅ 此处 key 仍用 _uid,确保 React 正确复用 DOM;
✅ Suspense 作用域聚焦于单个 Conto 组件,避免全局 loading 覆盖;
✅ fallback 可替换为骨架屏(Skeleton)提升体验。

⚠️ 关键注意事项

  • 防抖与节流:本例未加节流,实际项目建议对 handleScroll 使用 lodash.throttle(如 throttle(handleScroll, 100))防止高频触发;
  • 错误处理:真实 fetch 场景需捕获网络错误,设置重试机制及错误 UI;
  • 取消请求:若使用 fetch/axios,务必在 useEffect 清理函数中 abort() 未完成请求,避免 setState 在卸载组件上调用;
  • 服务端分页支持:前端分页(slice)仅适用于小规模数据;大数据量必须配合后端 offset/limit 或游标分页(cursor-based),否则内存和性能堪忧;
  • 无障碍(a11y):添加 aria-busy="true" 到容器,屏幕阅读器可感知加载状态。

✅ 总结

从零实现 React 无限滚动,本质是三步闭环:
状态驱动 —— 管理当前页码、已加载数据、加载状态;
事件驱动 —— 精准监听容器滚动并判定触底;
增量更新 —— 安全合并新数据,避免重复加载与竞态问题。

无需黑科技,扎实运用 React Hook 与 DOM API 即可交付生产级体验。下一步,你可以将此逻辑封装为自定义 Hook(如 useInfiniteScroll),进一步提升复用性。

标签:# js  # 判断是否  # 第一页  # 而非  # 复用  # 下一页  # 分页  # 加载  # ui  # 异步  # dom  # 事件  # map  # 封装  # 为什么  # overflow  # 作用域  # win  # 路由  # ios  # ai  # 后端  # axios  # 大数据  # json  # 前端  # react  # 触底  # 闭环  # 你可以  
在线客服
服务热线

服务热线

4008888355

微信咨询
二维码
返回顶部
×二维码

截屏,微信识别二维码

打开微信

微信号已复制,请打开微信添加咨询详情!