第58章:useEffect の「これを見張ってて」リスト
「useEffect はなんとなく分かったけど、カッコの横にあるあの [] ってなに??🤔」
――この章は、その「ナゾのカッコ」、**依存配列(=見張りリスト)**にフォーカスします。
🎯 この章のゴール
この章が終わるころには…
useEffect(() => { ... }, [ここ])の 「[ここ] に何を書くか」 が分かる ✅- 「いつ
useEffectが実行されるか」を、自分でイメージできる ✅ - 「あ、これ依存配列に入れないとバグりそう💦」がなんとなく分かる ✅
を目指します ✨
1️⃣ 「見張りリスト(依存配列)」ってなに?📝
useEffect の基本の形はこんな感じでした:
import { useEffect } from "react";
useEffect(() => {
// ここに「副作用」の処理を書く
});
ここに 「これを見張っておいてね」リスト を渡せるのが、依存配列(dependency array) です。
useEffect(() => {
// 何かの処理
}, [count, keyword]);
[]の中に入っているcountやkeywordが 「見張る対象」 です。- React は、前回のレンダー時の
count/keywordと、今回のレンダー時の値を比べます。 - どれか1つでも変わっていたら →
useEffectの中身を実行します。(react.dev)
イメージとしては、
「この子たちの誰かが変化したら、またこのEffectを動かしてね」
という「監視カメラ」のリストだと思ってください 📹✨
2️⃣ いつ useEffect が動くの?図でイメージしよう 🎨
Mermaid でざっくり図にしてみます👇
ポイント💡
-
最初の1回(マウント時) は、前回の値が存在しないので、基本的に実行されます。
-
2回目以降は、前回の依存配列と今回の依存配列を、1つずつ比較 して、
- どれか1つでも違ったら → クリーンアップ → Effect本体を実行
- 全部同じだったら → スキップ
比較方法は Object.is という、ほぼ === と同じ比較を使っています。(react.dev)
3️⃣ 1つの値を見張る:カウンターで実験してみよう 🔢
まずは一番シンプルなパターンです。
✅ サンプル:count が変わったらだけログを出す
import { useState, useEffect } from "react";
export function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`count が変わりました!今の値: ${count}`);
}, [count]); // 👈 「count を見張る」依存配列
return (
<div>
<p>現在のカウント: {count}</p>
<button onClick={() => setCount((prev) => prev + 1)}>
1 増やす 👍
</button>
</div>
);
}
このときの動きはこんな感じ👇
- 初回表示時(
count = 0) → 実行(1回目) - ボタンを押して
count = 1→ 実行(2回目) - ボタンを押して
count = 2→ 実行(3回目) - …という感じで、
countが変わるたびにuseEffectが走ります。(dhiwise.com)
逆に言うと、他の state だけが変わって count が変わらなかったら、このEffectは動きません。
4️⃣ 複数の値を見張る:検索フォームの例 🔍
今度は 複数の値を見張る パターンです。
✅ サンプル:keyword or page が変わったら検索する(風)
import { useState, useEffect } from "react";
type SearchResult = {
id: number;
title: string;
};
export function SearchBox() {
const [keyword, setKeyword] = useState("React");
const [page, setPage] = useState(1);
const [results, setResults] = useState<SearchResult[]>([]);
useEffect(() => {
console.log("検索条件が変わったので、API を呼ぶイメージ ✨");
console.log(`keyword = ${keyword}, page = ${page}`);
// 実際のアプリではここで fetch などを書く
// 今回はダミーデータにしておく
const dummy: SearchResult[] = [
{ id: 1, title: `${keyword} の本 (ページ ${page})` },
{ id: 2, title: `${keyword} の入門記事 (ページ ${page})` },
];
setResults(dummy);
}, [keyword, page]); // 👈 両方を見張る
return (
<div>
<h2>検索ボックス 🔎</h2>
<div>
<label>
キーワード:
<input
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
/>
</label>
</div>
<div>
<button onClick={() => setPage((p) => Math.max(1, p - 1))}>
← 前のページ
</button>
<span style={{ margin: "0 8px" }}>ページ: {page}</span>
<button onClick={() => setPage((p) => p + 1)}>次のページ →</button>
</div>
<ul>
{results.map((item) => (
<li key={item.id}>📘 {item.title}</li>
))}
</ul>
</div>
);
}
ここでは、
keywordが変わる- または
pageが変わる
どちらか一方でも変化したら、Effect がもう一度走ります。
「この中の誰かが変わったら検索やり直しね!」 という感じです 🥰
5️⃣ 「変わったかどうか」はどう判定しているの?🧐
React は、依存配列の中を 1つずつ前回と今回で比較 しています。(react.dev)
number,string,booleanみたいなプリミティブ型は、値が違えば「変わった」と判定。object,array,functionなどのオブジェクト型は、参照(アドレス)が違えば変わったと判定。
⚠️ ここでちょっと注意!
const obj1 = { value: 1 };
const obj2 = { value: 1 };
obj1 === obj2; // false(中身同じでも別オブジェクト)
React も同じように、依存配列の比較で
[{ value: 1 }](前回)[{ value: 1 }](今回)
のように 新しく作り直されたオブジェクト が入ってくると、 「中身が一緒でも別物」と見なして、Effect を毎回実行してしまいます。(Qiita)
6️⃣ オブジェクト・配列を依存配列に入れるときの例 😵💫
❌ あまりよくない例
import { useState, useEffect } from "react";
export function BadExample() {
const [value, setValue] = useState(0);
useEffect(() => {
const options = { multiplier: 2 };
console.log("Effect 実行!", value * options.multiplier);
}, [value, { multiplier: 2 }]); // 😱 ここで毎回新しいオブジェクトを作ってる
return (
<button onClick={() => setValue((v) => v + 1)}>
クリック: {value}
</button>
);
}
{ multiplier: 2 } を直接依存配列に書いてしまうと、
レンダーのたびに毎回新しい { multiplier: 2 } が生成 されます。
→ つまり 毎回「変わった」と判定されてしまう ので、value が変わってなくても Effect が走ることがあります 🥲
✅ よりよい書き方の例
const multiplierOptions = { multiplier: 2 };
export function GoodExample() {
const [value, setValue] = useState(0);
useEffect(() => {
console.log("Effect 実行!", value * multiplierOptions.multiplier);
}, [value]); // 👈 ここは value だけでOK
return (
<button onClick={() => setValue((v) => v + 1)}>
クリック: {value}
</button>
);
}
- 変わらないオプションはコンポーネントの外に切り出すのがコツです 🧩
7️⃣ よくあるミス&「こう考えると楽」Tips 💡
❌ ミス1:依存配列を適当に空にしちゃう
useEffect(() => {
// state や props を使ってるのに…
}, []); // ← とりあえず [] にしちゃう
- こうすると、中で使っている値が変わってもEffectは走りません。
- そのため、「古い値のまま動き続ける」=バグ の原因になりがちです。(dhiwise.com)
👉 基本ルールとしては:
Effect の中で使っている「React の値(state, props, コンポーネント内で宣言した関数・変数など)」は、ぜんぶ依存配列に書く
と思っておくと安全です ✨ (詳しい話は後の章でやりますが、まずはこの直感でOK!)
8️⃣ ミニ演習 ✍️✨
ちょっと手を動かして、依存配列の「気持ち」をつかんでみましょう。
🎮 演習1:ドキュメントタイトルを count に合わせて変える
Counterコンポーネントを作る(さっきのサンプルをベースにしてOK)countが変わるたびにdocument.title = "カウント: X"に書き換える- 依存配列は
[count]にする
👉 ブラウザのタブのタイトルが、ボタンを押すたびに変わるはずです。
🎮 演習2:keyword だけを見張るバージョンを作る
- さっきの
SearchBoxをコピーする pageを state から消して、依存配列も[keyword]だけにする- キーワードを変えたときだけ、Effect が動くようにしてみる
👉 「どの値を見張るか」を自分で決める練習です 💪
🎮 演習3:オブジェクトを入れてみて挙動を観察する
- 適当なコンポーネントを作って、
- 依存配列に
{ foo: 1 }を直接書いた場合と、 - コンポーネント外に
const config = { foo: 1 };を置いて[config]にした場合で、 console.log("effect!")が何回出るか試してみる
👉 「参照が違うと毎回変わったとみなされる」という感覚をつかんでおきましょう 👀
まとめ 🎀
この章で覚えておいてほしいことは、だいたいこの4つです:
useEffectの第2引数の配列は、「これを見張ってて」リスト(依存配列) 📝- React は、前回と今回の依存配列を1個ずつ比較して、「変わったらEffect実行」「変わってなければスキップ」 している
- 数値や文字列は「値」が、オブジェクト・配列・関数は「参照」が変わるかどうかで判断される
- 基本は、Effect の中で使っている React の値を、ちゃんと依存配列に書く のが安全 ✅
次の章(第59章〜61章)では、
[][state]- 依存配列なし
の3パターンを、それぞれ具体的なコードと一緒にじっくり掘り下げていきます 🥳
ここまで来たあなたは、useEffect の半分くらいはもう理解できてます。
「見張りリスト」のイメージだけ、頭の片すみに置いておいてくださいね 🌸