包会的!用 React和IntersectionObserver API 封装一个高性能的无限滚动组件

182 阅读2分钟

无限滚动(Infinite Scroll)是一种常见的加载方式,适用于长列表数据,如社交媒体流、商品列表等。为了提升性能,我们需要一个高效的解决方案,避免一次性渲染过多 DOM 节点。

本文介绍如何使用 React 和 IntersectionObserver API 封装一个高性能的无限滚动组件。


需求分析

  • 懒加载数据:滚动到底部时自动加载。
  • 性能优化:减少 DOM 渲染,降低内存占用。
  • 支持容器滚动:支持窗口和容器滚动。
  • 兼容性好:使用现代 API,兼容性良好。

核心实现

1. 创建 useInfiniteScroll Hook

import { useEffect, useRef } from "react";

type Props = {
  loadMore: () => void;
  hasMore: boolean;
};

export const useInfiniteScroll = ({ loadMore, hasMore }: Props) => {
  const ref = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    if (!hasMore || !ref.current) return;
    
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) loadMore();
    });
    
    observer.observe(ref.current);
    return () => observer.disconnect();
  }, [loadMore, hasMore]);

  return ref;
};

2. 封装 InfiniteScroll 组件

import React from "react";
import { useInfiniteScroll } from "./useInfiniteScroll";

type Props<T> = {
  items: T[];
  renderItem: (item: T) => JSX.Element;
  loadMore: () => void;
  hasMore: boolean;
};

export const InfiniteScroll = <T,>({ items, renderItem, loadMore, hasMore }: Props<T>) => {
  const sentinelRef = useInfiniteScroll({ loadMore, hasMore });

  return (
    <div>
      {items.map(renderItem)}
      {hasMore && <div ref={sentinelRef} style={{ height: 1 }} />}
    </div>
  );
};

3. 使用示例

import React, { useState } from "react";
import { InfiniteScroll } from "./InfiniteScroll";

const fetchMockData = async (page: number) =>
  new Promise<number[]>((resolve) => setTimeout(() => resolve(Array.from({ length: 10 }, (_, i) => page * 10 + i)), 1000));

export const Demo = () => {
  const [items, setItems] = useState<number[]>([]);
  const [page, setPage] = useState(0);
  const [hasMore, setHasMore] = useState(true);

  const loadMore = async () => {
    const newData = await fetchMockData(page + 1);
    setItems((prev) => [...prev, ...newData]);
    setPage(page + 1);
    setHasMore(newData.length > 0);
  };

  return (
    <InfiniteScroll
      items={items}
      renderItem={(item) => <div key={item} style={{ padding: 10, borderBottom: "1px solid #ddd" }}>{item}</div>}
      loadMore={loadMore}
      hasMore={hasMore}
    />
  );
};

性能优化

  1. 使用 Intersection Observer 替代 onscroll 事件监听,提高性能。
  2. 减少 re-renderReact.memo 避免不必要的组件更新。
  3. 支持虚拟列表(可选):结合 react-window 提高渲染性能。

结语

本文介绍了如何封装一个高性能的 React 无限滚动组件,使用 useInfiniteScroll Hook 结合 Intersection Observer 监听滚动,同时进行了性能优化。

你可以根据需求扩展组件,例如上拉刷新、自定义加载动画等。如果觉得有帮助,欢迎点赞、收藏、交流!🚀