useEffectの依存配列(dependency array)は、Reactにおける副作用の再実行を制御する中心的な構造です。
にもかかわらず、多くのバグやパフォーマンス問題が「依存配列の指定ミス」から発生しています。

本記事ではレビューアー視点から、依存配列の設計ミスをどのように検出し、どう指摘すべきかを掘り下げていきます。

依存配列設計に潜む3つのリスク

1. 漏れ:必要な値が依存に含まれていない

→ 意図せぬ古い値で副作用が動き続ける

2. 過剰:本来関係ない値まで含めてしまう

→ 無意味な再実行が頻発しパフォーマンス悪化

3. 誤爆:オブジェクトや関数の再生成により不要な再実行が起きる

→ 毎回再描画される、無限ループに陥るなど

悪い例:依存漏れ
useEffect(() => {
  fetch(`/api/user/${userId}`);
}, []); // userIdが変わっても再実行されない
コードレビュー
@Reviewer: userIdが副作用の内部で使われているにもかかわらず、依存に含まれていません。これは重大なバグを引き起こす可能性があります。

useCallback・useMemoとの連携ミス

誤爆の例:関数依存
const fn = () => { ... };

useEffect(() => {
  fn();
}, [fn]); // fnは毎回再生成されるため、常に再実行される

このようなケースでは、関数やオブジェクトをuseCallbackuseMemoでメモ化しないと、依存が常に変わると判断されてしまいます。

メモ化による安定化
const fn = useCallback(() => { ... }, []);

useEffect(() => {
  fn();
}, [fn]); // 安定

ESLintの支援と限界

react-hooks/exhaustive-depsルールは、依存配列のミスを検知してくれます。
しかし、正しくESLintが働くように、副作用関数を純粋な構造に保つことも重要です。

アロー関数内で状態参照
useEffect(() => {
  const poll = () => {
    console.log(state.value); // 状態を直接参照
  };
  poll();
}, []); // stateが依存に入っていない

// → ESLintでも検出されない場合がある

図解:依存配列と再実行の関係性

UML Diagram

レビューで指摘すべき観点

依存配列レビューのテンプレート指摘

依存配列レビュー
@Reviewer: このuseEffect内では `userId` と `fn` の2つが使われていますが、依存配列にはどちらも含まれていません。意図しない再実行タイミングとなっており、バグの温床になります。
改善提案
- useEffect(() => { fetchUser(); }, []);
+ useEffect(() => { fetchUser(); }, [userId]);

まとめ

useEffectの依存配列は、Reactにおける副作用制御の中枢です。
レビューでは以下を必ず確認しましょう。

  • 依存に必要な変数がすべて含まれているか(漏れ)
  • 不要な値まで含まれていないか(過剰)
  • オブジェクトや関数が毎回再生成されていないか(誤爆)

これらを見落とすと、パフォーマンス悪化やバグにつながるだけでなく、副作用の再現性が崩れてレビュー不能になることもあります。

依存配列の設計は単なる記述ではなく、構造的安全性を保つためのレビュー対象であることを意識しましょう。