Back

Reactにおける無限スクロールの完全ガイド

Reactにおける無限スクロールの完全ガイド

無限スクロールは、ユーザーがページを下にスクロールすると新しいコンテンツが読み込まれるUXパターンです。ソーシャルフィード、タイムライン、ニュースアプリでよく見られ、ページネーションボタンを使わずに大規模なデータセットをスムーズに探索する方法です。このガイドでは、サードパーティライブラリとカスタムフックの両方を使用して、Reactで無限スクロールを構築する方法を学びます。

重要なポイント

  • Reactで無限スクロールを実装する2つの実用的な方法を学ぶ
  • IntersectionObserverを使用したカスタムフックまたはサードパーティパッケージを使用する
  • ローディング状態、ページネーション、空の結果の処理方法

無限スクロールとは?

無限スクロールは、ユーザーがリストの終わり近くまでスクロールすると新しいデータを読み込みます。ページネーションの必要性をなくし、連続的なブラウジング体験を作り出します。Instagram、Twitter、Redditなどのプロダクトでよく見られます。

方法1:パッケージの使用(react-infinite-scroll-component

Reactで無限スクロールを実装する最も簡単な方法は、十分にテストされたパッケージを使用することです。

ステップ1:インストール

npm install react-infinite-scroll-component

ステップ2:基本的な使用例

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

メリット

  • 素早くセットアップできる
  • ローディングと終了状態が組み込まれている

デメリット

  • スクロールロジックの制御が少ない
  • パッケージ依存関係が増える

方法2:IntersectionObserverを使用したカスタムフック

この方法では、完全な制御が可能で、余分な依存関係がありません。

ステップ1:フックの作成

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]);
}

ステップ2:コンポーネントでの使用

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に過負荷をかけないようにフェッチ呼び出しをデバウンスする
  • より良いUXのためにスピナーやスケルトンローダーを表示する
  • ネットワークエラーに対するリトライロジックを追加する
  • 結果がゼロや空のページなどのエッジケースを処理する
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`のような仮想化ツールを使用してください。

下部の`div`(センチネル)が表示されたときにデータ読み込みをトリガーするために`IntersectionObserver`を使用します。

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers