使用React打造高性能懒加载无限滚动组件

 更新时间:2026年05月08日 08:58:44   作者:Lee川  
本文将深入剖析一个基于 React 构建的高性能无限滚动组件,它利用现代浏览器的 Intersection Observer API,巧妙地替代了传统的滚动监听,实现了既优雅又高效的按需加载,感兴趣的小伙伴可以了解下

在现代 Web 开发中,性能优化用户体验往往是一对矛盾的统一体。我们既希望一次性给用户展示海量的数据(如社交媒体的动态流),又不希望页面因为加载过重而卡顿。为了解决这一问题,懒加载(Lazy Loading)无限滚动(Infinite Scroll) 应运而生。

今天,我们将深入剖析一个基于 React 构建的高性能无限滚动组件。它利用现代浏览器的 Intersection Observer API,巧妙地替代了传统的滚动监听,实现了既优雅又高效的“按需加载”。

组件内容

import { useRef,useEffect } from 'react';
// load more 通用组件
interface InfiniteScrollProps {
    hasMore: boolean; // 是否所以数据都加载了 分页
    isLoading?: boolean; // 滚动到底部加载更多 避免重复触发
    onLoadMore: () => void; // 更多加载的一个抽象 /api/posts?page=2&limit=10
    children: React.ReactNode; // InfiniteScroll 通用的滚动功能,滚动的具体内容接受定制
}
const InfiniteScroll:React.FC<InfiniteScrollProps> = ({
    hasMore,
    isLoading = false,
    onLoadMore,
    children,
}) => {
    // HTMLDivElement React 前端全局提供
    const sentinelRef = useRef<HTMLDivElement>(null);
    useEffect(() => {
        // dom, 组件挂载后
        if (!hasMore || isLoading) return; // 没有更多数据了 或者 加载中 不触发
        // IntersectionObserver 没有性能问题,不需要防抖节流
        const observer = new IntersectionObserver((entries) => {
            if (entries[0].isIntersecting) { // 是否进入视窗 viewport
                onLoadMore();
            }
        }, {
            threshold: 0, // 视窗进入 0% 就触发
        }
        );
        if(sentinelRef.current) {
            observer.observe(sentinelRef.current);
        }
        // 组件卸载时,断开观察(路由切换时,需要断开观察,否则会重复触发)
        return () => {
            if (sentinelRef.current) {
                observer.unobserve(sentinelRef.current);
            }
        }
    },[onLoadMore,hasMore,isLoading])
    // react 不建议直接访问dom,useRef
    return (
        <>
            {children}
            {/* Intersection Observer 哨兵元素 */}
        <div ref={sentinelRef} className="h-4" />
        {
            isLoading && (
                <div className='text-center py-4 text-sm text-muted-foreground'>
                    加载中...
                </div>
            )
        }
        </>
    )
}
export default InfiniteScroll;

核心概念:什么是 Intersection Observer?

在深入代码之前,我们需要理解一个关键概念:Intersection Observer(交叉观察器)

传统的无限滚动通常通过监听 windowscroll 事件实现。但这种做法存在性能隐患,因为滚动事件触发频率极高,频繁的 DOM 查询(getBoundingClientRect)会导致页面卡顿(俗称“掉帧”)。

Intersection Observer 是现代浏览器提供的原生 API,它允许我们异步监听目标元素是否进入视口,且完全不阻塞主线程,无需手动防抖(Debounce)。

核心角色:

  1. 目标元素(Target): 我们要观察的 DOM 节点。
  2. 根元素(Root): 观察的容器(通常是视口)。
  3. 阈值(Threshold): 目标元素与根元素相交的比例(0-1),达到该比例时触发回调。

代码深度解析

这段代码实现了一个通用的 React 函数组件,利用 TypeScript 定义了清晰的接口,封装了无限滚动的逻辑。

1. 接口定义:明确的契约

代码首先定义了 InfiniteScrollProps 接口,这是组件与外部交互的“契约”:

  • hasMore: boolean数据开关。指示是否还有更多数据可供加载。如果为 false,则停止一切观察行为。
  • isLoading?: boolean加载锁。标记当前是否正在加载数据。这能有效防止用户在快速滚动时触发重复的请求。
  • onLoadMore: () => void加载回调。当用户滚动到底部时,组件会调用此函数(通常用于发起 API 请求,如 /api/posts?page=2&limit=10)。
  • children: React.ReactNode内容占位。这是组件最灵活的部分,允许父组件传入任何需要展示的列表内容。

2. 核心逻辑:哨兵模式

组件内部使用了经典的“哨兵(Sentinel)”模式:

引用创建 (useRef):

const sentinelRef = useRef<HTMLDivElement>(null);

这里创建了一个对 DOM 元素的引用,用于后续的观察。

副作用管理 (useEffect):这是组件的“大脑”,负责观察器的生命周期管理:

守门人逻辑: if (!hasMore || isLoading) return;

如果数据已加载完或正在加载中,直接返回,避免无效的观察器创建。

观察器实例化:

const observer = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting) {
    onLoadMore(); // 触发加载
  }
}, { threshold: 0 });

这里创建了一个观察器实例。threshold: 0 意味着只要哨兵元素有 1 像素进入视口,就会触发回调。

观察与清理:组件挂载时开始观察哨兵元素,组件卸载时(return 函数)必须调用 observer.unobserve()。这是为了防止内存泄漏和路由切换后的重复触发。

3. JSX 结构:视图层

return (
  <>
    {children}
    <div ref={sentinelRef} className="h-4" />
    { isLoading && <div>加载中...</div> }
  </>
)
  • {children} :渲染传入的列表内容。
  • 哨兵元素:一个高度为 4px 的空 div,作为观察的目标。
  • 加载反馈:当 isLoading 为真时,展示“加载中...”的 UI,给用户明确的视觉反馈。

传统方案 vs. 本方案对比

为了更直观地理解这种实现的优势,我们可以通过下表进行对比:

特性传统 scroll 事件监听本方案 (Intersection Observer)
性能表现较差,需手动防抖,频繁重排重绘极佳,浏览器原生异步处理,无性能负担
代码复杂度高,需计算位置、处理兼容性低,声明式 API,逻辑清晰
触发机制主线程同步执行异步回调,不阻塞渲染
重复请求容易发生,需手动加锁易于控制,配合 isLoading 状态即可

总结

这个组件是一个典型的现代前端开发范例。它通过 TypeScript 提供了类型安全,利用 React Hooks 管理状态和副作用,并结合 Intersection Observer API 解决了性能痛点。

它不仅解决了长列表的性能瓶颈,还通过简洁的 API 设计(hasMore, isLoading, onLoadMore),让开发者可以轻松地将其集成到博客文章列表、电商商品流等各种场景中。这种“哨兵模式”是目前实现无限滚动的最佳实践之一。

到此这篇关于使用React打造高性能懒加载无限滚动组件的文章就介绍到这了,更多相关React懒加载无限滚动组件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • React 跨端动态化核心技术实例分析

    React 跨端动态化核心技术实例分析

    这篇文章主要为大家介绍了React 跨端动态化核心技术实例分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • 一起来了解React的Hook

    一起来了解React的Hook

    这篇文章主要为大家详细介绍了React的Hook,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • react hooks中的useState使用要点

    react hooks中的useState使用要点

    这篇文章主要为大家介绍了react hooks中的useState使用要点详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • 详解React 服务端渲染方案完美的解决方案

    详解React 服务端渲染方案完美的解决方案

    这篇文章主要介绍了详解React 服务端渲染方案完美的解决方案,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • react中useEffect Hook作用小结

    react中useEffect Hook作用小结

    React中useEffect是一个非常重要的Hook,它允许我们函数组件中执行副作用操作,本文就来介绍一下useEffect Hook作用,具有一定的参考价值,感兴趣的可以了解一下
    2024-11-11
  • React实现pc端的弹出框效果

    React实现pc端的弹出框效果

    这篇文章主要为大家详细介绍了React实现pc端的弹出框效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08
  • TypeScript在React项目中的使用实践总结

    TypeScript在React项目中的使用实践总结

    这篇文章主要介绍了TypeScript在React项目中的使用总结,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • vite+react+tailwindcss的简单使用方式

    vite+react+tailwindcss的简单使用方式

    这篇文章主要介绍了vite+react+tailwindcss的简单使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • React 高德地图进京证路线规划问题记录(汇总)

    React 高德地图进京证路线规划问题记录(汇总)

    这篇文章主要介绍了React高德地图进京证路线规划问题小记,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-08-08
  • 教你如何实现在react项目中嵌入Blazor

    教你如何实现在react项目中嵌入Blazor

    这篇文章主要介绍了如何实现在react现有项目中嵌入Blazor,通过这个案例我们可以知道 blazor也可以像react那样嵌入在任何的现有项目中,并且使用方便,需要的朋友可以参考下
    2023-01-01

最新评论