ホームページ >ウェブフロントエンド >jsチュートリアル >React での非同期データのフェッチとキャッシュのための軽量フックのショーケース

React での非同期データのフェッチとキャッシュのための軽量フックのショーケース

Mary-Kate Olsen
Mary-Kate Olsenオリジナル
2025-01-07 18:36:41476ブラウズ

Showcase of a Lightweight Hook for Async Data Fetching & Caching in React

皆さんこんにちは!

私は useAsync と呼ばれる軽量の React フックに取り組んできました。これは React Query の重要な機能 (フェッチ、キャッシュ、再試行など) の一部を模倣していますが、よりコンパクトです。簡単にカスタマイズできるパッケージ。以下は、関連するコードセクションを参照しながら、内部でどのように動作するかを簡単に説明したものです。コード全体を確認したい場合は、リポジトリに移動してください:

GitHub 上の完全なソース コード
このフックは、npm で api-refetch としても利用できます。


なぜ独自のフックを作るのか?

React Query と SWR はどちらも優れたライブラリですが、いくつかの理由から、より実践的なアプローチが必要でした。

  1. 軽量フットプリント

    React Query と SWR は機能が豊富ですが、比較的サイズが大きくなる可能性があります (React Query ~2.2 MB、SWR ~620 kB)。 api-refetch は約 250 KB であるため、バンドル サイズが大きな懸念事項となる 小規模なアプリに最適です。このフックは、別のライブラリ (Intlayer) の依存関係としてインストールされることを意図しています。結果として、ソリューションのサイズが重要な考慮事項となりました。

  2. カスタマイズと最適化が簡単
    ローカル ストレージからのデータの保存/取得や、単純なアプローチを使用した並列リクエストの管理など、いくつかの特定の機能が必要でした。
    リポジトリを複製するか、コードをプロジェクトに直接コピーすることで、不要な機能を削除し、必要なものだけを保持できます。これにより、バンドル サイズが削減されるだけでなく、不必要な再レンダリングと増加が最小限に抑えられ、特定の要件に合わせた、より効率的でパフォーマンスの高いソリューションが得られます。

  3. 必要なプロバイダーはありません

    フックをグローバルにするためにコンテキスト プロバイダーを避け、その使用法をできるだけシンプルにしたいと考えました。そこで、Zustand ストアに基づいてフックのバージョンを作成しました (以下の例を参照)。

  4. 学習演習

    非同期ライブラリを最初から構築することは、同時実行性、キャッシュ、状態管理の内部構造を理解するための優れた方法です

要するに、独自のフックを作成することは、ライブラリを小さく理解しやすく保ちながら、必要な機能に正確に焦点を当てる (必要ない機能はスキップする) 機会でした。

対象となる機能

React フックの管理:

  • フェッチと状態管理: ロード、エラー、成功、フェッチされた状態を処理します。
  • キャッシュとストレージ: オプションでデータをキャッシュし (React 状態または内部の Zustand 経由)、ローカル ストレージのサポートを提供します。
  • 再試行と再検証: 構成可能な再試行制限と自動再検証間隔。
  • アクティブ化と無効化: 他のクエリまたは状態に応じて、クエリを自動的にアクティブ化および無効化します。例: ユーザーがログインすると一部のデータが自動的に取得され、ユーザーがログアウトするとデータが無効になります。
  • 並列コンポーネント マウント フェッチ: 複数のコンポーネントが同時にマウントされる場合、同じリソースに対する複数の同時リクエストを防ぎます。

コードの仕組み

以下は、API 再取得の重要なポイントと、useAsync.tsx のコードの関連部分への短い参照です。

1. 並列マウントの取得と処理

  • コードスニペット:
  // This map stores any in-progress Promise to avoid sending parallel requests
  // for the same resource across multiple components.
  const pendingPromises = new Map();

  const fetch: T = async (...args) => {
    // Check if a request with the same key + args is already running
    if (pendingPromises.has(keyWithArgs)) {
      return pendingPromises.get(keyWithArgs);
    }

    // Otherwise, store a new Promise and execute
    const promise = (async () => {
      setQueryState(keyWithArgs, { isLoading: true });

      // ...perform fetch here...
    })();

    // Keep it in the map until it resolves or rejects
    pendingPromises.set(keyWithArgs, promise);
    return await promise;
  };
  • 説明: ここでは、進行中のフェッチを pendingPromises マップに保存します。 2 つのコンポーネントが (同じ keyWithArgs を持つことによって) 同じリソースを同時にフェッチしようとすると、2 番目のコンポーネントは重複したネットワーク呼び出しを行う代わりに、進行中のリクエストを再利用するだけです。

2. 再検証

  • コードスニペット:
  // Handle periodic revalidation if caching is enabled
  useEffect(
    () => {
      if (!revalidationEnabled || revalidateTime <= 0) return; // Revalidation is disabled
      if (!isEnabled || !enabled) return; // Hook is disabled
      if (isLoading) return; // Fetch is already in progress
      if (!isSuccess || !fetchedDateTime) return; // Should retry either of revalidate
      if (!(cacheEnabled || storeEnabled)) return; // Useless to revalidate if caching is disabled

      const timeout = setTimeout(() => {
        fetch(...storedArgsRef.current);
      }, revalidateTime);

      return () => clearTimeout(timeout);
    },
    [
      /* dependencies */
    ]
  );
  • 説明: 再検証を有効にするたびに、API-refetch はキャッシュされたデータが指定された revalidateTime よりも古いかどうかをチェックします。有効な場合、データはバックグラウンドで自動的に再取得され、追加の手動トリガーなしで UI の同期が維持されます。

3. リトライロジック

  • コードスニペット:
  useEffect(
    () => {
      const isRetryEnabled = errorCount > 0 && retryLimit > 0;
      const isRetryLimitReached = errorCount > retryLimit;

      if (!isEnabled || !enabled) return; // Hook is disabled
      if (!isRetryEnabled) return; // Retry is disabled
      if (isRetryLimitReached) return; // Retry limit has been reached
      if (!(cacheEnabled || storeEnabled)) return; // Useless to retry if caching is disabled
      if (isLoading) return; // Fetch is already in progress
      if (isSuccess) return; // Hook has already fetched successfully

      const timeout = setTimeout(() => {
        fetch(...storedArgsRef.current);
      }, retryTime);

      return () => clearTimeout(timeout);
    },
    [
      /* dependencies */
    ]
  );
  • 説明: エラーが発生した場合、フックは試行が失敗した回数をカウントします。まだ retryLimit を下回っている場合は、自動的に retryTime ミリ秒待ってから再試行します。このプロセスは、データが正常にフェッチされるか、再試行制限に達するまで継続されます。

4. 自動フェッチ

  • コードスニペット:
  // Auto-fetch data on hook mount if autoFetch is true
  useEffect(
    () => {
      if (!autoFetch) return; // Auto-fetch is disabled
      if (!isEnabled || !enabled) return; // Hook is disabled
      if (isFetched && !isInvalidated) return; // Hook have already fetched or invalidated
      if (isLoading) return; // Fetch is already in progress

      fetch(...storedArgsRef.current);
    },
    [
      /* dependencies */
    ]
  );
  • 説明: autoFetch を true に設定すると、フックはコンポーネントがマウントされるとすぐに非同期関数を自動的に実行します。これは、ロード時に常にデータが必要な「ファイア アンド フォーゲット」シナリオに最適です。

GitHub で完全なソースを参照してください

ローカル ストレージ ロジック、クエリの無効化などが含まれる完全なコードをここで確認してください:

  • 完全なソース コード

ご興味がございましたら、お気軽に試してみたり、問題を報告したり、貢献してください。フィードバックは大歓迎です!

使用例

インストール

コードをコピーするか、(リポジトリ)[https://github.com/aymericzip/api-refetch]をコード化します

または

  // This map stores any in-progress Promise to avoid sending parallel requests
  // for the same resource across multiple components.
  const pendingPromises = new Map();

  const fetch: T = async (...args) => {
    // Check if a request with the same key + args is already running
    if (pendingPromises.has(keyWithArgs)) {
      return pendingPromises.get(keyWithArgs);
    }

    // Otherwise, store a new Promise and execute
    const promise = (async () => {
      setQueryState(keyWithArgs, { isLoading: true });

      // ...perform fetch here...
    })();

    // Keep it in the map until it resolves or rejects
    pendingPromises.set(keyWithArgs, promise);
    return await promise;
  };

簡単な例

  // Handle periodic revalidation if caching is enabled
  useEffect(
    () => {
      if (!revalidationEnabled || revalidateTime <= 0) return; // Revalidation is disabled
      if (!isEnabled || !enabled) return; // Hook is disabled
      if (isLoading) return; // Fetch is already in progress
      if (!isSuccess || !fetchedDateTime) return; // Should retry either of revalidate
      if (!(cacheEnabled || storeEnabled)) return; // Useless to revalidate if caching is disabled

      const timeout = setTimeout(() => {
        fetch(...storedArgsRef.current);
      }, revalidateTime);

      return () => clearTimeout(timeout);
    },
    [
      /* dependencies */
    ]
  );

それだけです!試してみて、どうなるか教えてください。 GitHub でのフィードバック、質問、貢献を大歓迎です。

GitHub: api-refetch

コーディングを楽しんでください!

以上がReact での非同期データのフェッチとキャッシュのための軽量フックのショーケースの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。