レースコンディション

アプリケーション内のすべての可能なレースコンディションを排除することは不可能ですが、React Router は Web ユーザーインターフェースでよく見られる最も一般的なレースコンディションを自動的に処理します。

ブラウザの動作

React Router のネットワーク並行処理は、ドキュメントを処理する際の Web ブラウザの動作に大きく影響を受けています。

新しいドキュメントへのリンクをクリックし、新しいページが読み込みを完了する前に別のリンクをクリックすることを考えてみてください。ブラウザは次のようになります。

  1. 最初の要求をキャンセルする
  2. 新しいナビゲーションをすぐに処理する

同じ動作がフォーム送信にも適用されます。保留中のフォーム送信が新しい送信によって中断された場合、最初の送信はキャンセルされ、新しい送信がすぐに処理されます。

React Router の動作

ブラウザと同様に、リンクとフォーム送信による中断されたナビゲーションは、飛行中のデータ要求をキャンセルし、新しいイベントをすぐに処理します。

フェッチャーは、ナビゲーションのようなシングルトンイベントではないため、少しニュアンスが異なります。フェッチャーは他のフェッチャーインスタンスを中断することはできませんが、自分自身を中断することはでき、その動作は他のすべてと同じです。中断された要求をキャンセルし、新しい要求をすぐに処理します。

ただし、フェッチャーは再検証に関して相互に作用します。フェッチャーのアクション要求がブラウザに返された後、すべてのページデータの再検証が送信されます。これは、複数の再検証要求が同時に飛行中になる可能性があることを意味します。React Router は、すべての「新しい」再検証応答をコミットし、古い要求をキャンセルします。古い要求とは、返された要求よりも_前に_開始された要求のことです。

このネットワークの管理により、ネットワークのレースコンディションによって引き起こされる最も一般的な UI バグを防ぐことができます。

ネットワークは予測不可能であり、サーバーはこれらのキャンセルされた要求を処理するため、バックエンドでは依然としてレースコンディションが発生し、データ整合性の問題が発生する可能性があります。これらのリスクは、プレーン HTML の <forms> でデフォルトのブラウザ動作を使用する場合と同じリスクであり、低いと見なされ、React Router の範囲外です。

実用的な利点

タイプアヘッドコンボボックスの構築を考えてみましょう。ユーザーが入力すると、サーバーにリクエストを送信します。新しい文字を入力するたびに、新しいリクエストを送信します。テキストフィールドにない値の結果をユーザーに表示しないことが重要です。

フェッチャーを使用すると、これは自動的に管理されます。次の疑似コードを考えてみましょう。

// route("/city-search", "./search-cities.ts")
export async function loader({ request }) {
  const { searchParams } = new URL(request.url);
  return searchCities(searchParams.get("q"));
}
export function CitySearchCombobox() {
  const fetcher = useFetcher();
 
  return (
    <fetcher.Form action="/city-search">
      <Combobox aria-label="Cities">
        <ComboboxInput
          name="q"
          onChange={(event) =>
            // onChange でフォームを送信して都市のリストを取得する
            fetcher.submit(event.target.form)
          }
        />
 
        {fetcher.data ? (
          <ComboboxPopover className="shadow-popup">
            {fetcher.data.length > 0 ? (
              <ComboboxList>
                {fetcher.data.map((city) => (
                  <ComboboxOption
                    key={city.id}
                    value={city.name}
                  />
                ))}
              </ComboboxList>
            ) : (
              <span>結果が見つかりません</span>
            )}
          </ComboboxPopover>
        ) : null}
      </Combobox>
    </fetcher.Form>
  );
}

fetcher.submit の呼び出しは、そのフェッチャーの保留中のリクエストを自動的にキャンセルします。これにより、異なる入力値のリクエストの結果をユーザーに表示することがなくなります。