useEffect 完全入門【React】副作用処理をやさしく解説

ReactのuseEffectとは何か、使い方・依存配列・クリーンアップ関数を初心者向けに解説。よくあるハマりポイントと解決策もあわせて紹介します。

#react#hooks#useeffect#javascript#frontend

Reactを勉強していると必ずぶつかるのが useEffect

「なんとなく使えてるけど、ちゃんと理解できていない気がする……」という方は多いのではないでしょうか。依存配列の書き方を間違えて無限ループを起こしたり、クリーンアップをし忘れてバグを出したり……。

この記事では、useEffect の仕組みをゼロから丁寧に解説します。

この記事でわかること:

  • useEffect が必要な理由(副作用とは何か)
  • 基本的な書き方とパラメータの意味
  • 依存配列のパターンと使い分け
  • クリーンアップ関数の役割
  • よくある使用例(APIフェッチ・イベントリスナー・タイマー)
  • ハマりやすいポイントと解決策

「副作用」って何?

useEffect を理解するためにまず知っておきたいのが副作用(Side Effect)という概念です。

Reactコンポーネントの本来の仕事は「画面を描画すること」です。propsやstateを受け取って、JSXを返す——これがコンポーネントの純粋な役割です。

一方、画面の描画とは関係のない処理のことを副作用と呼びます。

副作用の例具体的な内容
API通信サーバーからデータを取得する
タイマーsetTimeout / setInterval を使う
イベントリスナーaddEventListener で操作を検知する
DOM操作ブラウザのタイトルを変更するなど
外部ライブラリチャットや地図などのSDKを初期化する

これらの処理をコンポーネントの描画処理の外で安全に実行する場所を提供するのが useEffect です。

基本的な書き方

useEffect(setup関数, 依存配列);

実際のコードはこんな形です:

import { useEffect } from "react";
 
function MyComponent() {
  useEffect(() => {
    // ここに副作用の処理を書く
    console.log("コンポーネントが表示されました!");
 
    // クリーンアップ関数(省略可能)
    return () => {
      console.log("コンポーネントが消えました!");
    };
  }, []); // 依存配列
 
  return <div>Hello!</div>;
}

2つのパラメータを理解する

① setup関数(第1引数)

副作用の処理を書く関数です。オプションでクリーンアップ関数return できます(後述)。

② 依存配列(第2引数)

「どのタイミングで副作用を再実行するか」を制御する配列です。書き方によって動作が変わります。

依存配列のパターン

依存配列の書き方は3パターンあります。

パターン1:空配列 []

useEffect(() => {
  console.log("初回表示のときだけ実行");
}, []); // ← 空配列

初回表示(マウント)時のみ実行されます。データの初回取得などに使います。

パターン2:値を入れた配列 [value]

useEffect(() => {
  console.log(`userIdが変わった: ${userId}`);
}, [userId]); // ← 監視したい値を入れる

初回 + 配列内の値が変わるたびに実行されます。特定の値に連動した処理に使います。

パターン3:依存配列なし

useEffect(() => {
  console.log("毎回のレンダリング後に実行");
}); // ← 第2引数を省略

レンダリングのたびに毎回実行されます。ほとんどのケースで使うことはありません。

依存配列実行タイミングよく使う場面
[]初回のみ初期データ取得・ライブラリ初期化
[value]初回 + value変更時特定値に連動した処理
なし毎回ほぼ使わない

クリーンアップ関数

setup関数の中で return した関数がクリーンアップ関数です。

useEffect(() => {
  // セットアップ
  const timerId = setInterval(() => {
    console.log("1秒経過");
  }, 1000);
 
  // クリーンアップ(コンポーネントが消えるとき・再実行前に呼ばれる)
  return () => {
    clearInterval(timerId); // タイマーを解除する
  };
}, []);

クリーンアップ関数が呼ばれるのは以下のタイミングです:

  1. コンポーネントがアンマウント(削除)されるとき
  2. 依存配列の値が変わって、エフェクトが再実行される直前

クリーンアップをし忘れると、コンポーネントが消えた後もタイマーやイベントリスナーが動き続けてしまいます。

動作の流れ

useEffect のライフサイクルを整理するとこうなります:

① コンポーネントが表示される(マウント)
      ↓
  setup関数が実行される

② propsやstateが変わってコンポーネントが更新される
      ↓
  依存配列の値が変わっていたら…
  → クリーンアップ関数が実行される
  → setup関数が再実行される

③ コンポーネントが消える(アンマウント)
      ↓
  クリーンアップ関数が実行される

よくある使用例

APIからデータを取得する

最もよく使うパターンです。

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
 
  useEffect(() => {
    setLoading(true);
 
    fetch(`https://api.example.com/users/${userId}`)
      .then((res) => res.json())
      .then((data) => {
        setUser(data);
        setLoading(false);
      });
  }, [userId]); // userIdが変わったら再取得
 
  if (loading) return <p>読み込み中...</p>;
  return <h1>{user.name}</h1>;
}

イベントリスナーを登録・解除する

function WindowSize() {
  const [width, setWidth] = useState(window.innerWidth);
 
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
 
    // イベントリスナーを登録
    window.addEventListener("resize", handleResize);
 
    // クリーンアップで必ず解除する
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []); // 初回のみ登録でOK
 
  return <p>画面幅: {width}px</p>;
}

ページタイトルを変更する

function ArticlePage({ title }) {
  useEffect(() => {
    document.title = `${title} | My Blog`;
 
    // ページ離脱時にタイトルを元に戻す
    return () => {
      document.title = "My Blog";
    };
  }, [title]);
 
  return <h1>{title}</h1>;
}

ハマりやすいポイントと解決策

❌ 問題1:無限ループになる

// NG:stateを依存配列に入れて、そのstateをエフェクト内で更新している
useEffect(() => {
  setCount(count + 1); // countが変わる → エフェクト再実行 → 無限ループ!
}, [count]);

解決策:state更新関数の関数形式を使い、依存配列から外す。

// OK:関数形式を使えばcountを依存配列に入れなくていい
useEffect(() => {
  const timerId = setInterval(() => {
    setCount((prev) => prev + 1); // 前の値を受け取る形式
  }, 1000);
  return () => clearInterval(timerId);
}, []); // 依存配列が空でOK

❌ 問題2:オブジェクト・関数を依存配列に入れてしまう

// NG:optionsはレンダリングのたびに新しいオブジェクトが作られる
function MyComponent({ options }) {
  useEffect(() => {
    doSomething(options);
  }, [options]); // 毎回再実行されてしまう
}

解決策:オブジェクトの中のプリミティブな値(文字列・数値など)を依存配列に入れる。

// OK:具体的な値を指定する
useEffect(() => {
  doSomething({ size: options.size, color: options.color });
}, [options.size, options.color]); // 特定のプロパティを指定

❌ 問題3:開発中に2回実行される(Strict Mode)

開発環境(React.StrictMode)では、バグを発見するために意図的にセットアップ→クリーンアップ→セットアップの順で2回実行されます。

// 開発環境での実行順序
// 1. セットアップ実行
// 2. クリーンアップ実行(Strict Modeによる検査)
// 3. セットアップ実行(本来の実行)

これは本番環境では起きません。クリーンアップ関数を正しく書いていれば問題ないので、慌てなくて大丈夫です。

useEffectを使いすぎないようにしよう

useEffect は便利ですが、本当に必要な場面だけで使うのが大切です。

公式ドキュメントでも「エフェクトはReactパラダイムからの脱出ハッチ」と表現されており、多用は禁物です。

以下のような処理は useEffect を使わなくても書けます:

// ❌ NG:propsからstateを計算するだけならuseEffectは不要
useEffect(() => {
  setFullName(firstName + " " + lastName);
}, [firstName, lastName]);
 
// ✅ OK:レンダリング中に直接計算する
const fullName = firstName + " " + lastName;

まとめ

useEffect は最初は難しく感じますが、3つのポイントを押さえておけば大丈夫です:

  • setup関数:副作用の処理を書く場所
  • 依存配列:いつ再実行するかを制御する([] / [value] / なし)
  • クリーンアップ関数:後始末を return で返す

最初は [](初回のみ)と [value](値変化時)の2パターンだけ覚えて、実際にAPIフェッチなどで使ってみることをおすすめします。

PR

React公式ドキュメントで詳細を確認しよう

useEffectのインタラクティブなサンプルや、より深い仕組みの解説は公式ドキュメントで確認できます。

useEffect公式ドキュメントを見る

関連記事