Reactにおける非同期の闇を制御せよ:AbortControllerが「真の堅牢性」をもたらす理由
Reactの`useEffect`を使いこなしていると自負するエンジニアほど、一度は「レースコンディション(競合状態)」の泥沼に足を取られたことがあるはずです。ユーザーが素早くタブを切り替えたとき、あるいは検索フォームでタイピングを繰り返したとき。古いリクエストの結果が後から到着し、UIを不整合な状態に書き換えてしまう——。
これを単なる「おまじない」のようなフラグ管理で解決しようとするのは、もうやめましょう。今回は、ブラウザの標準機能である`AbortController`を使い、Reactのライフサイクルと非同期処理を正しく調停する方法について、アーキテクチャの観点から深掘りします。
—
なぜ「fetchのキャンセル」が必須なのか
多くの開発者は、`useEffect`の中で非同期関数を呼び出し、その結果を`setState`に渡すだけで満足します。しかし、これは極めて危険な設計です。
1. メモリリークの温床: コンポーネントがアンマウントされた後も、JavaScriptの実行コンテキストは非同期処理の結果を待ち続けます。結果が返ってきた瞬間に`setState`が呼ばれ、Reactは警告を発します(かつてはエラーでした)。
2. ネットワークリソースの浪費: 必要なくなったリクエストをブラウザが律儀に処理し続けるのは、モバイル端末においてはバッテリーと帯域の無駄遣いです。
3. 状態の不整合: 前の非同期処理が遅延し、最新の処理よりも後に完了した場合、ユーザーは「古いデータ」を見せられることになります。これはUX上の重大なバグです。
実践:AbortControllerを用いた堅牢な実装
`AbortController`は、DOM APIの一部として提供されている「シグナル伝達メカニズム」です。これを使うことで、非同期処理の実行権をReactのライフサイクルへと移譲できます。
import { useState, useEffect } from ‘react’;
const UserProfile = ({ userId }) => {
const [data, setData] = useState(null);
useEffect(() => {
// 1. コントローラーのインスタンスを作成
const controller = new AbortController();
const { signal } = controller;
const fetchData = async () => {
try {
// 2. fetchのオプションにsignalを渡す
const response = await fetch(`/api/users/${userId}`, { signal });
const result = await response.json();
setData(result);
} catch (err) {
// 3. fetchがキャンセルされるとAbortErrorが投げられる
if (err.name === ‘AbortError’) {
console.log(‘フェッチは正常にキャンセルされました’);
} else {
console.error(‘予期せぬエラー:’, err);
}
}
};
fetchData();
// 4. クリーンアップ関数でAbortシグナルを送出
// コンポーネントがアンマウントされるか、userIdが変更されると発火
return () => controller.abort();
}, [userId]);
return
;
};
アーキテクトの視点:内部挙動と最適化の勘所
このコードが優れているのは、React側の「副作用の完了」を待たずに、ブラウザレベルでネットワーク接続を即座に切断できる点です。
1. レースコンディションの完全制圧
`useEffect`の依存配列に`userId`を指定している場合、`userId`が変わるたびにクリーンアップ関数が実行されます。つまり、新しいリクエストが飛ぶ前に、前のリクエストが確実にキャンセルされる。これにより、複数のリクエストが競合する余地が物理的に消滅します。
2. クリーンアップ関数の重要性
React 18以降、`Strict Mode`下ではコンポーネントが意図的に「マウント→アンマウント→再マウント」を繰り返します。`AbortController`を導入していないと、開発環境でリクエストが二重に走り、デバッグを困難にする原因となります。クリーンアップ関数は「お掃除」のためだけではなく、「予測可能な副作用の管理」のために不可欠なのです。
3. パフォーマンスとリソース最適化
この手法は単にバグを防ぐだけではありません。不要なPromiseの解決を抑制し、メインスレッドの負荷を軽減します。大規模なデータフェッチを伴うアプリケーションでは、この小さな実装の積み重ねが、ブラウザのメモリ使用量とレンダリングの安定性に直結します。
まとめ:上級者として次のステップへ
もしあなたが、さらに洗練されたアーキテクチャを目指すのであれば、この`AbortController`のロジックをカスタムフックとして抽出することをお勧めします。
「コンポーネントに副作用の管理をさせない」ことこそが、クリーンなアーキテクチャへの近道です。`useFetch`のような汎用フックにこのロジックをカプセル化し、呼び出し側には「結果」と「ローディング状態」だけを返す。そうすれば、複雑なコンポーネントでも副作用の管理に悩まされることはありません。
Reactは、標準APIを泥臭く使い倒すエンジニアにこそ、その真の力を貸してくれるライブラリです。明日のコードから、ぜひこの「キャンセルする勇気」を取り入れてみてください。

コメント