こんにちは。君たちの書いたコードレビューをしていると、時々「おや?」と首を傾げたくなる実装に出くわすことがある。特に`useEffect`の依存配列(dependency array)の扱い方には、そのエンジニアの「Reactのライフサイクルに対する理解の深さ」がモロに出るんだよね。
今日は、その中でも「依存配列をあえて省略した場合の挙動」について、現場のリアルな実務目線で徹底的に解剖していこうと思う。
「あれ、配列を渡し忘れたらどうなるんだっけ?」なんて新人時代のうっかりで済ませてはいけない。裏側でブラウザやReactのエンジンがどう暴れ回っているのか、そしてなぜそれがパフォーマンスの致命傷になり得るのか。しっかりと頭に叩き込んでくれ。
—
1. 依存配列を省略するとはどういうことか?(基本のおさらい)
まず、基本の確認だ。`useEffect`の第二引数に何も渡さない、つまりこういう状態を指す。
useEffect(() => {
// 毎回レンダリング走るたびに呼ばれる処理
});
第2引数に「空の配列 `[]`」を渡せば「マウント時(初回)の1回だけ」になるのは君たちも知っていると思う。しかし、依存配列そのものを完全にオミット(省略)すると、Reactは「このコンポーネントが画面を描画(レンダリング)するたびに、毎回この副作用を実行しろ」と解釈する。
ステートが更新されようが、親から渡ってくるプロパティが変わろうが、関係ない。画面が1ピクセルでも再描画されようものなら、容赦なくこのエフェクトが発動する仕組みになっているんだ。
—
2. ブラウザの裏側で何が起きているのか?(パフォーマンスへの影響)
「毎回実行されるくらい、大したことないでしょ?」と思ったそこの君。ちょっと待ってほしい。ブラウザのレンダリングパイプラインを思い出してほしい。
Reactのライフサイクルにおいて、`useEffect`の中身はブラウザが画面を描画し終えた後(paintの後)に非同期で実行される。
もし依存配列を省略した重い処理(例えば、DOMの強制的な計測や、重い計算処理、不要なイベントリスナーの再登録など)がコンポーネント内にあると、どうなるか。
1. ユーザーがボタンをクリックしてステートが変わる。
2. ReactがVirtual DOMを再構築し、差分を実際のDOMに反映する(Commit Phase)。
3. ブラウザが画面を描画する(Paint)。
4. 【ここで省略された`useEffect`が爆誕】毎回のレンダリングのたびに重い副作用が走り、メインスレッドをブロックする。
5. 次のユーザー操作に対する反応がもたつく(カクつき、Jankの発生)。
これが、実務でアプリが「なんか最近重いぞ」と言われる原因の温床だ。DOMの書き換えと副作用の無限ループに近い実行が、ブラウザのメインスレッドをいじめることになる。
—
3. 避けるべきケースと、現場でやりがちなアンチパターン
実務でやりがちなのが、「とりあえず最新のステート値を確実に `useEffect` 内で拾いたいから、依存配列を取っ払っちゃえ」という安易な妥協だ。これは最悪の悪手。
🚨 やりがちなアンチパターン
import React, { useState, useEffect } from ‘react’;
export const BadCounter = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState(”);
// ⚠️ 依存配列がないため、text(テキスト入力)が1文字変わるだけでも、
// このエフェクトが走ってしまう!
useEffect(() => {
console.log(`現在のカウント: ${count}`);
document.title = `Count: ${count}`;
}); // <-- 依存配列がまるごと無い!
return (
Count: {count}
setText(e.target.value)}
placeholder=”何か入力してね”
/>
);
};
上記のコードでは、ユーザーがテキストボックスに文字を入力するたびに `setText` が走り、コンポーネントが再描画され、無関係なはずの `useEffect` まで毎回実行される。これがパフォーマンス低下のジワジワとした毒になるんだ。
—
4. 正しいアプローチ:では、いつ依存配列を省略してもいいのか?
答えはシンプルで、「実務において、依存配列を意図的に省略すべきケースはほぼゼロに近い」と言っていい。
もし「毎回のレンダリングごとにどうしても実行したい処理」があるなら、それは本当に `useEffect` であるべきか疑うべきだ。例えば、レンダーのたびに何らかの値に基づいた変数を同期的に計算したいだけなら、エフェクトを使う必要すらない(レンダリング中に直接計算すればいい)。
どうしても副作用として毎回のレンダリングをフックしたい稀なケース(例えば、全ての描画後にDOMの位置を正確に測定し直すライブラリのラッパーなど)以外では、必ず依存配列を明示するか、適切なフック(`useLayoutEffect` など)への切り替えを検討しよう。
✨ 綺麗な修正コード例
先ほどのコードを、正しく依存配列をコントロールした形にリファクタリングしてみよう。
import React, { useState, useEffect } from ‘react’;
export const GoodCounter = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState(”);
// ✅ 依存配列に [count] を明示する
// これにより、countが変化した時(かつマウント時)のみ副作用が走る。
// テキストボックスに入力しても、このエフェクトは無駄に実行されない!
useEffect(() => {
console.log(`カウントが更新されました: ${count}`);
document.title = `Count: ${count}`;
}, [count]); // 依存する値だけを正確に指定する
return (
Count: {count}
setText(e.target.value)}
placeholder=”何か入力してね”
/>
);
};
これだけで、不要な再実行を防ぎ、Reactのパフォーマンスを健全に保つことができる。
—
まとめ:シニアからのメッセージ
Reactの hooks は非常に強力で、魔法のように動く。だからこそ、内部の仕組み(「いつ、何がトリガーされて動いているのか」)をブラックボックスにしたままコーディングしていると、スケールするアプリケーションを作った時に必ず痛い目をみるんだ。
- 依存配列を省略すると、すべてのレンダリング後に副作用が実行される。
- それはブラウザのメインスレッドに負荷をかけ、UIのカクつき(パフォーマンス低下)に直結する。
- 「なぜこの依存関係が必要なのか」を常に意識し、ESLint(`react-hooks/exhaustive-deps`)の警告を無視しないこと。
日々の実装で「動けばいいや」ではなく、「なぜ動くのか、どう効率的なのか」を突き詰める姿勢を忘れないでほしい。君たちの書くコードのクオリティが、プロダクトの命運を握っているんだからね。さて、今日のレビューはこのへんで。また何かあったらいつでも質問にきなさい。

コメント