Reactの useEffect をレビューする際、最も見落とされやすく、かつ深刻な影響を与えるのが「依存配列の不備」です。依存すべき値が記載されていない、あるいは不要な値が記載されている場合、レンダリングのタイミングや副作用の発火条件が意図と食い違い、バグの温床になります。

本記事では、useEffect の依存配列に関する設計ミスを構造レベルで見抜く方法を、レビューアー視点で解説していきます。

技術背景:Reactの依存配列が担う構造的責任

useEffect の第二引数は依存配列(deps)と呼ばれ、Effectの実行タイミングを制御する役割を持っています。

  • 空配列:初回マウント時にのみ実行される
  • 値を含む配列:その値が変化するたびに再実行される
  • 省略:すべてのレンダリング後に実行(非推奨)
依存配列とは

依存配列とは、useEffect(fn, [dep1, dep2]) の形式で与える第二引数であり、Effect内で参照する「再実行のトリガー」となる変数群です。これが不完全だと、Reactの最適化ロジックに逆らい、意図しない副作用の実行が発生します。

React公式は依存配列を「副作用関数が参照しているすべての値を含めるべき」と明言していますが、現場ではこの原則が守られていないケースが非常に多いです。

よくある依存配列ミスとその構造的背景

ケース1:参照値が依存配列に含まれていない

漏れのある依存配列
useEffect(() => {
  if (user.role === "admin") {
    fetchAdminConfig();
  }
}, []);  // ← userが入っていない
コードレビュー
@Reviewer: userオブジェクトを参照しているにもかかわらず、依存配列に含まれていません。これはメモリリークや古いデータ利用の原因になります。

構造的に見ると、このEffectは「user.role」という条件付き副作用を含んでおり、userという依存元と副作用の責務が乖離しているのが問題です。

ケース2:関数の依存を誤って省略している

関数依存の省略
useEffect(() => {
  doSomething();
}, []); // ← doSomethingがuseCallbackでもメモ化されていない
コードレビュー
@Reviewer: `doSomething` はuseEffect外で定義されていますが、再定義の可能性があるため依存配列に含めるべきです。useCallbackでメモ化するか、依存として明記してください。

このパターンは、「安定した参照」である前提が壊れた時点で副作用が不整合になるため、コードの可読性が高くても設計として脆弱です。

なぜレビューアーは構造で判断すべきか?

依存配列の誤りは、見た目の挙動ではすぐには分かりません。静的解析ツール(ESLintのreact-hooks/exhaustive-deps)を導入していないプロジェクトでは特に、レビューで見抜くしか手段がありません。

構造的な判断基準は以下の通りです:

  • Effect関数内にあるすべての外部参照は、明示的に依存に含める必要がある
  • ただし、安定している参照(定数・useRefなど)は除外可能
  • useCallbackやuseMemoに依存している関数は、それ自体が依存となる

実務でありがちな「依存の過少・過剰」例とレビュー方法

過少依存(依存の記載漏れ)

過少依存の例
useEffect(() => {
  if (isReady && flag) {
    doSomething();
  }
}, [isReady]); // ← flagが依存にない
コードレビュー
@Reviewer: 条件判定に `flag` を用いているにも関わらず、依存配列に含まれていません。ロジックの分岐条件が一致しない可能性があります。

過剰依存(意味のない依存)

過剰依存の例
useEffect(() => {
  console.log("Mounted");
}, [Math.random()]);
コードレビュー
@Reviewer: `Math.random()` は再評価されるたびに新しい値になるため、意図せずEffectが毎回走る構造になります。依存に含めるべきではありません。

可視化:依存構造を図で見る

UML Diagram

Hookの安定性と依存の関係

副作用関数内で利用する関数を useCallback でメモ化していない場合、その関数は毎回新しく定義されるため、参照の変更によってEffectが再実行される原因になります。

関数の安定性と依存
const handleClick = () => {
  console.log("clicked");
};

useEffect(() => {
  doSomething(handleClick);
}, [handleClick]); // ← 無限ループの原因
コードレビュー
@Reviewer: handleClickが毎レンダリングで再生成されているため、useEffectが毎回再評価されます。useCallbackで安定化させるか、useEffect外で定義して依存を解消してください。

依存関係の過不足をレビューで判断するコツ

レビュー時は、以下のように読み取ると依存漏れを見抜きやすくなります。

  • Effect関数内のすべての外部変数参照を列挙する
  • useRefの .current 参照は依存に含めない(値は安定)
  • JSON.stringify(obj) などの比較が依存を混乱させる可能性があるので、比較ロジックの代替提案も検討する

まとめ:依存配列レビューの指摘観点

  • 副作用が参照しているすべての変数・関数が依存配列に含まれているか?
  • 安定していない関数が依存から漏れていないか?
  • useRefなどの安定変数を誤って依存に入れていないか?
  • 過剰な依存(毎回変わる値やランダム値)を記述していないか?

レビューアーは構造を読み解くスキルが問われます。依存配列はただの文法でなく、設計思想の反映であることを意識すると、より精度の高いレビューが可能になります。