React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React懒加载无限滚动组件

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

作者: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 接口,这是组件与外部交互的“契约”:

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> }
  </>
)

传统方案 vs. 本方案对比

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

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

总结

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

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

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

您可能感兴趣的文章:
阅读全文