カテゴリ: React 更新日: 2026/02/11

ReactのuseEffectで非同期処理をキャンセルする方法を初心者向けに解説

useEffectで非同期処理をキャンセルする方法
useEffectで非同期処理をキャンセルする方法

先生と生徒の会話形式で理解しよう

生徒

「先生、Reactでデータを取ってくる処理を書いたんですが、画面を閉じたあとにも動き続けてしまいました。どうすれば止められるんですか?」

先生

「それは非同期処理をキャンセルしていないからですね。ReactではuseEffectのクリーンアップを使ってキャンセルする方法があります。」

生徒

「キャンセルってどういう意味ですか?」

先生

「たとえるなら、宅配を頼んだ後に外出して受け取れないとわかったらキャンセルしますよね。プログラムでも、もう必要ない処理は途中で止めるのが大切なんです。」

1. 非同期処理とキャンセルの必要性

1. 非同期処理とキャンセルの必要性
1. 非同期処理とキャンセルの必要性

非同期処理とは、時間がかかる処理を待たずに進める仕組みです。例えば、サーバーからデータを取得するfetch関数などが代表例です。しかし、ユーザーが画面を移動したあともその処理が続いてしまうと、不要なエラーやメモリの無駄遣いが発生します。

そのため、ReactではuseEffectのクリーンアップ関数を使って、処理を途中でキャンセルできるようにします。

2. AbortControllerを使ったキャンセル

2. AbortControllerを使ったキャンセル
2. AbortControllerを使ったキャンセル

AbortControllerというブラウザ標準の機能を使うと、fetchの通信を途中で止められます。ReactのuseEffectと組み合わせると、安全にキャンセル処理を書けます。


import React, { useEffect, useState } from "react";

function App() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    fetch("https://jsonplaceholder.typicode.com/posts/1", { signal })
      .then((res) => res.json())
      .then((result) => setData(result))
      .catch((error) => {
        if (error.name === "AbortError") {
          console.log("通信をキャンセルしました");
        } else {
          console.error(error);
        }
      });

    return () => controller.abort();
  }, []);

  return (
    <div>
      <h1>データ表示</h1>
      {data ? <p>{data.title}</p> : <p>読み込み中...</p>}
    </div>
  );
}

export default App;
(画面に「データ表示」と見出しが出て、数秒後にサーバーから取得したタイトルが表示されます。画面を閉じた場合は通信がキャンセルされます)

3. フラグを使ったシンプルなキャンセル方法

3. フラグを使ったシンプルなキャンセル方法
3. フラグを使ったシンプルなキャンセル方法

AbortControllerを使わなくても、フラグを使って「この処理はもう不要」と判断する方法もあります。これは古くから使われているシンプルなやり方です。


import React, { useEffect, useState } from "react";

function App() {
  const [data, setData] = useState(null);

  useEffect(() => {
    let isActive = true;

    fetch("https://jsonplaceholder.typicode.com/posts/2")
      .then((res) => res.json())
      .then((result) => {
        if (isActive) {
          setData(result);
        }
      });

    return () => {
      isActive = false;
    };
  }, []);

  return (
    <div>
      <h1>記事データ</h1>
      {data ? <p>{data.title}</p> : <p>読み込み中...</p>}
    </div>
  );
}

export default App;
(画面に「記事データ」と見出しが出て、サーバーから取得した記事タイトルが表示されます。画面を閉じるとフラグによって更新処理が止まります)

4. 実際の開発での利用場面

4. 実際の開発での利用場面
4. 実際の開発での利用場面

非同期処理のキャンセルは次のような場面で役立ちます。

  • 検索フォームで入力が変わるたびにデータを取得する場合
  • ページを移動するときに不要な通信を止めたい場合
  • 大量のデータを扱うアプリで無駄な処理を避けたい場合

これらを正しく扱うと、アプリは無駄な負担が減り、ユーザー体験が良くなります。

5. 初心者が注意すべきポイント

5. 初心者が注意すべきポイント
5. 初心者が注意すべきポイント

キャンセル処理を忘れると、Reactで「アンマウント後に状態を更新しようとしました」という警告が出ることがあります。これは、画面が閉じたあとにもsetStateが呼ばれているからです。

このエラーを防ぐには、必ずクリーンアップ処理を使うこと、そして非同期処理を安全に止める習慣をつけることが大切です。

まとめ

まとめ
まとめ

React開発において、useEffect内での非同期処理の制御は、アプリケーションの品質を左右する非常に重要な要素です。今回の記事では、データの取得中にコンポーネントが画面から消えてしまった(アンマウントされた)際に、どのようにしてその処理を中断させるかという「キャンセル処理」に焦点を当てて解説してきました。

フロントエンド開発、特にシングルページアプリケーション(SPA)では、ユーザーは頻繁にページやタブを切り替えます。そのたびにサーバーへのリクエストが走り、もしキャンセル処理が行われていないと、もはや存在しないコンポーネントに対して「データを画面に反映させて!」という命令が飛び続けてしまいます。これが、よく見かける「メモリリーク」や「不要なエラーログ」の原因となります。

非同期処理キャンセルの2大手法の比較

本記事で紹介した2つの手法は、どちらも現場で頻繁に使われるものです。それぞれの特徴を整理してみましょう。

手法 メリット デメリット
AbortController ネットワーク通信自体を即座に中断できるため、通信リソースを節約できる。 catchブロックでのエラーハンドリング(AbortErrorの判定)が必要。
フラグ管理(isActive) ロジックが単純で分かりやすく、fetch以外の非同期処理にも応用しやすい。 通信自体は裏側で最後まで続いてしまうため、リソース消費の改善にはならない。

実践的な実装例:カスタムフックへの応用

実際のプロジェクトでは、これら一連のキャンセル処理を毎回書くのは大変です。そのため、以下のようにカスタムフックとして共通化することが推奨されます。ここでは、より実戦に近いTypeScript(React TSX)での記述例を見てみましょう。


import { useState, useEffect } from "react";

// カスタムフック:データを安全に取得する
function useFetchData(url: string) {
  const [data, setData] = useState<any>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();
    const { signal } = controller;

    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await fetch(url, { signal });
        if (!response.ok) {
          throw new Error("ネットワーク応答が正常ではありません");
        }
        const json = await response.json();
        setData(json);
        setError(null);
      } catch (err: any) {
        if (err.name === "AbortError") {
          console.log("フェッチがキャンセルされました");
        } else {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    // クリーンアップ関数で通信を中断
    return () => {
      controller.abort();
    };
  }, [url]);

  return { data, loading, error };
}

export default useFetchData;
(このカスタムフックを使用すると、URLが変わるたびに自動で前の通信をキャンセルし、新しいデータを取得し直します。ローディング状態やエラーハンドリングも一元管理できます)

パフォーマンスとユーザー体験の向上に向けて

Reactのフック(Hooks)を使いこなすということは、単に動くコードを書くだけでなく、アプリケーションが「背負っている負荷」をいかに軽くするかを考えることでもあります。 特に、検索エンジンのSEO対策が必要な公開サイトや、モバイル端末で利用されるWebアプリでは、無駄なデータ転送量を削ることが読み込み速度の向上に直結します。useEffectのクリーンアップは、そのための第一歩です。

また、最新のReact開発では「React Query」や「SWR」といった、強力なデータ取得ライブラリが使われることも増えています。これらのライブラリは、内部で今回解説したようなキャンセル処理を自動的に行ってくれます。しかし、その根本的な仕組みを知っているのと知らないのとでは、トラブルが起きた際の原因特定能力に大きな差が出ます。

基本をしっかりと押さえた上で、より高度なライブラリやフレームワーク(Next.jsなど)へとステップアップしていきましょう。コードの一行一行がユーザーのストレスを減らすことに繋がっていると意識すると、プログラミングがもっと楽しくなるはずです。

先生と生徒の振り返り会話

生徒

「先生、ありがとうございました!useEffectの中でreturnを使って関数を返すだけで、あんなに大事なキャンセル処理ができるなんて驚きでした。」

先生

「そうですね。クリーンアップ関数は『後片付け』の役割を担っています。これを使わないと、プログラムが散らかり放題になって、最終的にアプリが重くなったり動かなくなったりするんですよ。」

生徒

「さっきのAbortControllerの話ですが、エラーハンドリングの中でAbortErrorかどうかをチェックするのはなぜですか?」

先生

「良い質問ですね!キャンセルというのは、通信エラーのような『失敗』ではなく、意図的に『止めた』だけなんです。だから、それを普通のエラーとして画面に出してしまうとユーザーが混乱してしまいます。それを防ぐために区別しているんですよ。」

生徒

「なるほど。お掃除と同じで、次に使う時に困らないように、終わった後のことを考えるのがプロの書き方なんですね。Next.jsとかを使っていても、この考え方は同じですか?」

先生

「その通りです。フレームワークが変わっても、非同期処理のライフサイクルを管理するという原則は変わりません。まずはこの基本を自分のものにして、どんな環境でも安定したアプリを作れるようになりましょう!」

生徒

「はい!これからは『やりっぱなし』にしないコードを心がけます。次はもっと複雑なAPI連携にも挑戦してみたいです!」

カテゴリの一覧へ
新着記事
New1
React
Reactコンポーネントの再利用と分割を完全マスター!初心者でもわかるコンポーネント設計
New2
React
ReactでAxiosインターセプターの使い方を完全ガイド!初心者でもわかるリクエストとレスポンスの処理方法
New3
React
JSXの書き方!初心者でもわかるReactタグと属性の基本ルール解説
New4
React
ReactとDockerを使った開発環境構築の基本を徹底解説!初心者でもわかるReactとDockerの連携方法
人気記事
No.1
Java&Spring記事人気No1
React
ReactでonChangeイベントを使ってフォーム入力値を管理する方法を初心者向けに解説
No.2
Java&Spring記事人気No2
React
ReactとTypeScriptの環境構築をやさしく解説!Viteとtsconfigの設定も丁寧に紹介
No.3
Java&Spring記事人気No3
React
Reactのイベントハンドリングのアンチパターンまとめ!初心者でもわかる注意点
No.4
Java&Spring記事人気No4
React
Reactのカスタムフックの作り方を完全ガイド!再利用可能なロジックを切り出す仕組み
No.5
Java&Spring記事人気No5
React
ReactでFetch APIのローディング状態を管理する方法|初心者にもわかる解説
No.6
Java&Spring記事人気No6
React
create-react-appでReactプロジェクトを作成する手順を初心者向けに完全解説!
No.7
Java&Spring記事人気No7
React
ReactのuseStateとuseEffectでよくあるエラーと解決方法ガイド!初心者でもわかるReactフック
No.8
Java&Spring記事人気No8
React
Reactでファイルアップロードを実装する方法を解説!Fetch APIで画像やPDFを送る仕組みを初心者向けに紹介