結論:useEffectで迷う原因は「ライフサイクル思考」と「依存配列の誤解」
Reactを学び始めてしばらくすると、多くの人が同じ壁にぶつかります。
それがuseEffectの使い方がよく分からない問題です。
- 動いてはいるが、なぜ動くのか説明できない
- ESLintに言われるがまま依存配列を書いている
- 無限ループが起きて慌てて依存配列を空にする
こうした経験がある方は少なくないでしょう。
結論から言うと、useEffectで迷う最大の理由は次の2つです。
- Reactを「ライフサイクルベース」で理解しようとしている
- 依存配列を「実行タイミング指定」だと誤解している
この記事では、なぜuseEffectが分かりづらいのかを構造的に整理し、
実際に書きがちなコード例と、その結果どうなるかを交えながら解説します。
なぜuseEffectはこんなに分かりづらいのか
理由1:class時代のライフサイクルの代替だと思っている
Reactをclassコンポーネントから学んだ人ほど、useEffectをこう捉えがちです。
- componentDidMount の代わり
- componentDidUpdate の代わり
- componentWillUnmount の代わり
確かに「結果」だけを見ると似ています。
しかし、考え方の起点がまったく違います。
classのライフサイクルは
「いつ実行されるか」を意識する仕組みでした。
一方でuseEffectは、
「どんな副作用が、どの値に依存しているか」
を宣言する仕組みです。
ここを取り違えると、useEffectは一気に難しくなります。
理由2:依存配列を実行タイミング指定だと思っている
次のコードは、非常によく見かけます。
useEffect(() => { fetchData(); }, []);
「空配列だから初回だけ実行される」
この説明自体は間違いではありません。
しかし本質は、
「この副作用は、どのstateやpropsにも依存していない」
とReactに伝えているだけです。
この理解がないまま、次のようなコードを書くと問題が起きます。
useEffect(() => { fetchUser(userId); }, []);
userIdが変わっても再実行されません。
これは依存関係の宣言として誤っているからです。
実際によくある失敗例とその結果
失敗例1:とりあえず依存配列を空にする
useEffect(() => { setCount(count + 1); }, []);
初心者がほぼ必ず通るコードです。
結果として、
- countは1のまま
- なぜ更新されないのか分からない
という状態になります。
これは、useEffect内で参照しているcountが
初回レンダリング時の値で固定されているためです。
失敗例2:ESLintに従って無限ループ
useEffect(() => { setCount(count + 1); }, [count]);
今度は無限ループです。
これは
「countが変わる → effect実行 → countが変わる」
を繰り返しているだけです。
useEffect内で更新しているstateを、そのまま依存配列に入れるのは危険
という代表的なパターンです。
useEffectは「状態変化への反応」を書く場所
useEffectを理解するための最重要ポイントはこれです。
- この副作用は、何が変わったら再実行されるべきか?
例えば、
- userIdが変わったらユーザー情報を取得したい
のであれば、答えは明確です。
useEffect(() => { fetchUser(userId); }, [userId]);
これ以上でも、これ以下でもありません。
副作用と計算処理を混同しない
次のようなコードは、useEffectを書く必要がありません。
const total = price * count;
これは副作用ではなく、単なる計算です。
useEffectに入れた瞬間、設計が歪みます。
useEffectが向いている人・向いていない人
useEffectが向いている人
- Reactの再レンダリングの仕組みを理解している
- 「何に依存しているか」で考えられる
- 副作用と純粋な処理を分けて考えられる
このタイプの人にとって、useEffectは非常に強力な道具になります。
useEffectが向いていない人
- 処理の実行順を厳密に追いたい
- 命令的なコードで考えたい
- JavaScriptのクロージャに慣れていない
この場合、無理にuseEffectを多用するとバグの温床になります。
実務で必ず知っておきたい注意点
useEffectの乱用は可読性を下げる
- データ取得
- 状態同期
- フォーム初期化
これらを1コンポーネントに詰め込むと、
「なぜこの処理が動いているのか」が分からなくなります。
必要に応じて、
- カスタムフックに切り出す
- useMemo / useCallback を検討する
といった設計が重要です。
まとめ:結局どうすればいいか
useEffectで迷わなくなるために、意識すべきことは3つです。
- useEffectはライフサイクルではない
- 依存配列は「実行タイミング指定」ではない
- 副作用だけを書く場所だと割り切る
この視点を持つだけで、
「よく分からないuseEffect」は確実に減ります。