Reactの「依存配列」という名の時限爆弾:なぜexhaustive-depsを無視してはいけないのか
現場でコードレビューをしていると、必ずと言っていいほど遭遇するのが `eslint-plugin-react-hooks` の `exhaustive-deps` ルールとの戦いです。
「とりあえず `[]` を空にして警告を黙らせよう」「eslint-disableすれば動くでしょ」……そうやって積み上げたコードが、半年後に「なぜか特定の操作でバグる」「画面がちらつく」といった、再現性の低い怪奇現象として牙を剥く。そんな光景を何度も見てきました。
今日は、なぜこのルールが単なる「お節介な警告」ではなく、Reactの生命線なのか。そして、この泥沼にハマったときにどう脱出するのか、プロの視点で解き明かしていきます。
—
1. なぜ「依存配列」の省略が「死」を招くのか
まず、Reactの `useEffect` は「コンポーネントのレンダリング結果」と「外部の同期処理」を繋ぐための接着剤です。
依存配列 `[deps]` は、Reactに対して「この変数が変わった時だけ、この処理をやり直してくれ」という契約書を渡しているようなものです。もしここで嘘をついて(依存関係を省略して)古い変数を参照し続けるとどうなるか。
ブラウザの裏側では、古いクロージャがメモリに残り続け、本来なら更新されているはずの最新のstateやpropsを、Reactが「変化なし」と判断して無視し続けることになります。結果、ユーザーがボタンを押しても画面が更新されない、あるいは古いデータで通信が走るといった、致命的な不整合が生まれます。
「動いているからいい」は、単に「バグが発覚していないだけ」なのです。
—
2. 「eslint-disable」を打つ前に考えるべきこと
警告が出たからといって、すぐ `// eslint-disable-line` を打つのは素人のやることです。まずは、「なぜこの変数が依存配列に入っているのか?」を疑ってください。多くの場合、解決策は3つに集約されます。
A. 関数をコンポーネントの外に出すか、useCallbackで包む
関数がレンダリングのたびに再生成されているのが原因なら、`useCallback` でメモ化するか、いっそコンポーネント外の純粋関数として切り出しましょう。
B. functional update(関数型更新)を使う
`state` そのものに依存しているせいで警告が出るなら、`setState` の引数に関数を渡すパターンを使いましょう。これで `state` 自体を依存配列から排除できます。
C. useRefで「同期しなくていい値」を管理する
「値は参照したいが、更新をトリガーにしたくない」というケースもあります。この場合は `useRef` の出番です。
—
3. 実践:現場で使える「依存配列」攻略サンプル
以下に、よくあるバグを回避しつつ、綺麗に依存配列を管理するコード例を置いておきます。
import { useState, useEffect, useCallback, useRef } from ‘react’;
const UserProfile = ({ userId }) => {
const [count, setCount] = useState(0);
// useRefを使って「変更をトリガーにしないが、最新値は持っておきたい」値を保持
const latestUserId = useRef(userId);
useEffect(() => {
latestUserId.current = userId;
}, [userId]);
// 関数をuseCallbackでメモ化し、安定させる
const fetchData = useCallback(() => {
console.log(`fetching data for: ${latestUserId.current}`);
// ここでAPIコールなどを実行
}, []); // userIdに依存しないため、配列は空でOK
useEffect(() => {
// fetchDataはメモ化されているため、このeffectはuserIdの変化で再発火しない
fetchData();
}, [fetchData]);
return (
Count: {count}
{/ 関数型更新を使うことで、[count]を依存配列に入れずに済む /}
);
};
—
4. まとめ:ルールは「足かせ」ではなく「羅針盤」
`exhaustive-deps` が警告を出すのは、あなたのコードの設計が「Reactのライフサイクルから逸脱し始めている」というサインです。
1. 依存配列を隠さない: 警告は無視せず、設計を見直すチャンスと捉える。
2. 関数の再生成を疑う: `useCallback` で安定させ、コンポーネントの構造を整理する。
3. 状態管理を見直す: `useState` の関数型更新や `useRef` を使い、「本当に同期が必要な値は何か」を突き詰める。
Reactは、ある程度自由が効く分、書き手の力量がモロに出るフレームワークです。ルールを「守らされるもの」ではなく「より堅牢なアプリを作るための武器」として使いこなせるようになれば、あなたも一人前のReactアーキテクトです。
明日からのコードレビューで、この視点をぜひ役立ててみてください。応援しています。

コメント