0Pricing
React Academy · レッスン

競合状態とEffectのクリーンアップ

useEffectでAbortControllerを使い、再レンダー時に古いfetchリクエストをキャンセルします。

「競合状態とEffectのクリーンアップ」はCoddyKit上の無料React Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはReact Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 React Academyコースには全4レッスンが含まれています。

ようこそ

このレッスンでは、データを取得するReactのエフェクトで競合状態がどのように発生するか、そしてクリーンアップにAbortControllerを使ってそれを解消する方法を学びます。

競合状態とは

競合状態とは、2つの非同期処理が予測できない順序で完了することです。Reactでは、ユーザーが項目間をすばやく移動すると、項目Aの遅いfetchが項目Bのfetchより後に完了し、古いデータが表示されることがあります。

バグ:古いfetchのレスポンス

クリーンアップがない状態で、古いfetchが完了する前にコンポーネントが新しいIDで再レンダリングされると、両方のfetchが実行されます。古くて遅いfetchが、状態に入っている新しいデータを上書きする可能性があります。
useEffect(() => {
  fetch('/api/item/' + id)
    .then(r => r.json())
    .then(data => setData(data)); // may run AFTER a newer fetch!
}, [id]);

修正方法1:無視フラグ

シンプルな修正方法は、エフェクト内でブール値の`ignore`フラグを設定することです。クリーンアップ関数でそれをtrueに設定します。fetchのコールバックでは、setStateを呼び出す前にフラグを確認します。
useEffect(() => {
  let ignore = false;
  fetch('/api/item/' + id)
    .then(r => r.json())
    .then(data => { if (!ignore) setData(data); });
  return () => { ignore = true; };
}, [id]);

修正方法2:AbortController

AbortControllerは現在の標準的な方法です。エフェクト内でコントローラーを作成し、そのsignalをfetchに渡して、クリーンアップ関数で中止します。fetchはAbortErrorをスローするため、無視できます。
useEffect(() => {
  const controller = new AbortController();
  fetch('/api/item/' + id, { signal: controller.signal })
    .then(r => r.json())
    .then(data => setData(data))
    .catch(err => {
      if (err.name === 'AbortError') return; // expected
      setError(err.message);
    });
  return () => controller.abort();
}, [id]);

async/awaitでのAbortController

useEffect内の非同期関数ではtry/catchを使ってください。catchブロックではAbortErrorを無視します。
useEffect(() => {
  const ctrl = new AbortController();
  async function load() {
    try {
      const res = await fetch('/api/' + id, { signal: ctrl.signal });
      const json = await res.json();
      setData(json);
    } catch (e) {
      if (e.name !== 'AbortError') setError(e.message);
    }
  }
  load();
  return () => ctrl.abort();
}, [id]);

クリーンアップは常に必要です

競合状態が起こりにくい場合(マウント時に1回だけfetchする場合など)でも、必ずクリーンアップを追加してください。React 18のStrictModeではエフェクトが2回実行されるため、クリーンアップがないと、最初の中断されたリクエストが状態を汚染します。

useEffectでの非同期処理の落とし穴

useEffectのコールバックを`async`にすることはできません。エフェクト内で非同期関数を定義してすぐに呼び出すか、.then()/.catch()を使った通常の関数を使用してください。
useEffect(() => {
  // useEffect callback is NOT async
  async function fetchData() { /* ... */ }
  fetchData(); // call it
  return () => ctrl.abort();
}, [id]);

ライブラリに任せることができます

React Query、SWR、RTK Queryは、競合状態、中止、クリーンアップをすべて自動的に処理します。これらのライブラリを使う場合、AbortControllerのコードを手動で記述する必要はありません。

競合状態のテスト

競合状態を手動でテストするには、Network DevToolsを開き、「Slow 3G」にスロットリングを設定して、項目間をすばやくクリックします。クリーンアップがない場合は、古いデータが一瞬表示されることを確認できます。AbortControllerを使うと、最後に選択した項目だけが表示されます。

理解度チェック

useEffectのクリーンアップ関数でcontroller.abort()を呼び出すと、何が起こりますか?

まとめ

競合状態は、古いfetchのレスポンスが新しいfetchの後に状態を更新すると発生します。無視フラグまたはAbortControllerで修正してください。useEffectのクリーンアップでは、必ず中止するか無視するようにします。React Queryなどのライブラリを使えば、これを自動的に処理できます。

次のレッスン

次のレッスンは、**ブロッキングしないUI更新のためのuseTransition**です。重い状態更新をstartTransitionでラップし、UIの応答性を維持する方法を学びます。

よくある質問

「競合状態とEffectのクリーンアップ」レッスンは無料ですか?

はい。「競合状態とEffectのクリーンアップ」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、React Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 React Academyコースには全4レッスンが含まれています。

「競合状態とEffectのクリーンアップ」で何を学びますか?

useEffectでAbortControllerを使い、再レンダー時に古いfetchリクエストをキャンセルします。 ブラウザで直接実行するハンズオンコードでReact Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

React Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのReact Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「競合状態とEffectのクリーンアップ」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このReact Academyレッスンでコードを書いて実行できますか?

はい。すべてのReact Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 競合状態とEffectのクリーンアップ
  2. ブロッキングしないUI更新のためのuseTransition
  3. 入力のデバウンスに使うuseDeferredValue
  4. データとコードの境界に使うSuspense
← React Academyに戻る