useEffect依存配列の設計とレビュー観点:漏れ・過剰・誤爆の三大リスクにどう向き合うか
useEffectの依存配列(dependency array)は、Reactにおける副作用の再実行を制御する中心的な構造です。
にもかかわらず、多くのバグやパフォーマンス問題が「依存配列の指定ミス」から発生しています。
本記事ではレビューアー視点から、依存配列の設計ミスをどのように検出し、どう指摘すべきかを掘り下げていきます。
依存配列設計に潜む3つのリスク
1. 漏れ:必要な値が依存に含まれていない
→ 意図せぬ古い値で副作用が動き続ける
2. 過剰:本来関係ない値まで含めてしまう
→ 無意味な再実行が頻発しパフォーマンス悪化
3. 誤爆:オブジェクトや関数の再生成により不要な再実行が起きる
→ 毎回再描画される、無限ループに陥るなど
悪い例:依存漏れ
useEffect(() => {
fetch(`/api/user/${userId}`);
}, []); // userIdが変わっても再実行されない
コードレビュー
@Reviewer: userIdが副作用の内部で使われているにもかかわらず、依存に含まれていません。これは重大なバグを引き起こす可能性があります。
useCallback・useMemoとの連携ミス
誤爆の例:関数依存
const fn = () => { ... };
useEffect(() => {
fn();
}, [fn]); // fnは毎回再生成されるため、常に再実行される
このようなケースでは、関数やオブジェクトをuseCallback
やuseMemo
でメモ化しないと、依存が常に変わると判断されてしまいます。
メモ化による安定化
const fn = useCallback(() => { ... }, []);
useEffect(() => {
fn();
}, [fn]); // 安定
ESLintの支援と限界
react-hooks/exhaustive-deps
ルールは、依存配列のミスを検知してくれます。
しかし、正しくESLintが働くように、副作用関数を純粋な構造に保つことも重要です。
アロー関数内で状態参照
useEffect(() => {
const poll = () => {
console.log(state.value); // 状態を直接参照
};
poll();
}, []); // stateが依存に入っていない
// → ESLintでも検出されない場合がある
図解:依存配列と再実行の関係性
レビューで指摘すべき観点
依存配列レビューのテンプレート指摘
依存配列レビュー
@Reviewer: このuseEffect内では `userId` と `fn` の2つが使われていますが、依存配列にはどちらも含まれていません。意図しない再実行タイミングとなっており、バグの温床になります。
改善提案
- useEffect(() => { fetchUser(); }, []);
+ useEffect(() => { fetchUser(); }, [userId]);
まとめ
useEffectの依存配列は、Reactにおける副作用制御の中枢です。
レビューでは以下を必ず確認しましょう。
- 依存に必要な変数がすべて含まれているか(漏れ)
- 不要な値まで含まれていないか(過剰)
- オブジェクトや関数が毎回再生成されていないか(誤爆)
これらを見落とすと、パフォーマンス悪化やバグにつながるだけでなく、副作用の再現性が崩れてレビュー不能になることもあります。
依存配列の設計は単なる記述ではなく、構造的安全性を保つためのレビュー対象であることを意識しましょう。