useEffect 完全入門【React】副作用処理をやさしく解説
ReactのuseEffectとは何か、使い方・依存配列・クリーンアップ関数を初心者向けに解説。よくあるハマりポイントと解決策もあわせて紹介します。
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); // タイマーを解除する
};
}, []);クリーンアップ関数が呼ばれるのは以下のタイミングです:
- コンポーネントがアンマウント(削除)されるとき
- 依存配列の値が変わって、エフェクトが再実行される直前
クリーンアップをし忘れると、コンポーネントが消えた後もタイマーやイベントリスナーが動き続けてしまいます。
動作の流れ
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フェッチなどで使ってみることをおすすめします。