【実務・中級編】 AbortControllerを用いたフェッチのキャンセル – React実践ガイド

useEffectの「魔境」を突破する:AbortControllerでフェッチを制御する真の作法

React開発に慣れてくると、誰もが一度は直面する壁がある。そう、`useEffect` 内での非同期処理と、コンポーネントのアンマウント(あるいは再レンダリング)に伴う「レースコンディション」だ。

「あれ、ボタンを押したはずなのに、古いリクエストの結果が表示されてデータが化けたぞ?」
「コンポーネントが消えた後にステートを更新しようとして、Reactの警告(Memory leak warning)がコンソールを埋め尽くしている…」

これらは、Reactを使いこなす上で避けては通れない通過儀礼だ。今日は、この泥沼から脱出し、ブラウザの力を最大限に引き出すための `AbortController` を使った堅牢な実装パターンを伝授しよう。

—

なぜ、ただの `useEffect` では不十分なのか?

初心者がやりがちなのが、フラグ変数(`isMounted`のようなboolean)を立てて、コンポーネントが消えたら更新を止めるという手法だ。しかし、これは「JavaScript側の制御」に過ぎない。

裏側で何が起きているか?
実は、JavaScriptで `fetch` を呼んだ時点で、ブラウザのネットワーク層にはHTTPリクエストが飛んでいる。React側でステート更新をガードしても、ネットワーク通信自体はブラウザのバックグラウンドで生き続け、通信量を消費し、最終的に不要なレスポンスが返ってくる。

ここで登場するのが `AbortController` だ。これは「ネットワークリクエスト自体をブラウザレベルで強制終了させる」ための標準APIだ。

現場で即戦力となる実装パターン

以下は、コンポーネントのアンマウントや、依存配列の変更によって「前のリクエストが不要になった」瞬間に、通信をスパッと切り捨てるための定石コードだ。

import { useState, useEffect } from ‘react’;

const UserProfile = ({ userId }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);

useEffect(() => {
// 1. コントローラーを生成
const controller = new AbortController();
const { signal } = controller;

const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`, { signal });
const result = await response.json();
setData(result);
} catch (err) {
// 2. キャンセルされた場合はエラーとして吐かれるので握りつぶす
if (err.name === ‘AbortError’) {
console.log(‘通信がキャンセルされました’);
return;
}
// それ以外のエラーは適切にハンドリング
console.error(‘フェッチエラー:’, err);
} finally {
setLoading(false);
}
};

fetchData();

// 3. クリーンアップ関数でキャンセルを実行
// これにより、userIdが変更された時やコンポーネントがアンマウントされた時に
// ブラウザへ「このリクエストはもう要らない」と信号を送る
return () => controller.abort();
}, [userId]); // userIdが変わるたびに前のリクエストはキャンセルされる

return

{loading ? ‘読み込み中…’ : JSON.stringify(data)}

;
};

—

このコードが「プロの設計」である理由

1. クリーンアップの実行タイミング: Reactは `useEffect` が再実行される直前、あるいはコンポーネントがアンマウントされる直前に、クリーンアップ関数を呼び出す。この「戻り値の関数」に `controller.abort()` を仕込むことで、Reactのライフサイクルとブラウザのネットワーク制御を完璧に同期させている。
2. `AbortError` のハンドリング: `fetch` がキャンセルされると、Promiseは `AbortError` を投げて拒否される。これを単なる「通信エラー」と区別して握りつぶすのが肝だ。これを行わないと、コンソールに不要なエラーログが流れてしまい、他の重大なエラーを見逃す原因になる。
3. 無駄なレンダリングの抑制: コンポーネントが破棄された後に `setData` を呼ぶという悲しい事故を、そもそも通信自体を止めることで物理的に防いでいる。これがフロントエンドの「防御的プログラミング」だ。

さらなる高みへ:ライブラリという選択肢

ここまで読んで「毎回こんなボイラープレートを書くのは面倒だ」と思ったなら、君は正しい。実務では、`TanStack Query (React Query)` や `SWR` を使うのが今のスタンダードだ。

これらのライブラリは、内部で `AbortController` などの仕組みを抽象化し、さらに強力なキャッシュ戦略や重複リクエストの抑制機能を提供してくれる。しかし、「ライブラリの裏で何が起きているか」を理解しているか否かで、トラブルシューティングの速度は雲泥の差が出る。

まずは今回紹介した素の `fetch + AbortController` を自分で書いてみて、ネットワークタブの「Pending」がキャンセルされる様子を眺めてみてほしい。その体験こそが、君をただの「React書き」から「Reactアーキテクト」へと押し上げるはずだ。

現場からは以上だ。また何かあればいつでも聞いてくれ。

コメント

タイトルとURLをコピーしました