0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

useEffect の依存配列について

Posted at

useEffect の依存配列は、「いつ useEffect の中の処理を実行するか」を React に伝えるための重要な仕組みです。

基本構文

useEffect(() => {
  // 副作用の処理(例:API 呼び出し、イベント)
}, [依存変数1, 依存変数2, ...]);

依存配列の意味

依存配列の書き方 実行されるタイミング
[](空配列) 初回マウント時のみ。1回だけ実行される。
[依存変数1, 依存変数2] 依存変数1 または 依存変数2変化したとき に実行される。
書かない(省略) 毎回レンダリング後に実行される。非推奨

依存配列が必要な場面

① API呼び出しなどの非同期処理

例: 特定のパラメータが変わったときに API を再取得したい。

useEffect(() => {
  fetch(`/api/data?query=${query}`).then(...);
}, [query]);  // query が変わるたびに再実行

ポイント

  • query が変わったときだけリクエストしたい。
  • 依存配列がないと、毎回レンダリングでリクエストしてしまいパフォーマンス悪化。

② イベントリスナーの登録・解除

例: ある条件でスクロールイベントを登録したい。

useEffect(() => {
  const onScroll = () => console.log("scrolled!");
  window.addEventListener("scroll", onScroll);
  return () => window.removeEventListener("scroll", onScroll);
}, [enabled]);  // enabled の変化に応じてイベントを切り替え

ポイント

  • 状態に応じてイベント登録を切り替える必要がある。
  • 依存配列がないと、意図しないタイミングでイベントが追加・削除されない。

③ setInterval / setTimeout の使用

useEffect(() => {
  const timer = setInterval(() => {
    console.log(count);
  }, 1000);
  return () => clearInterval(timer);
}, [count]);

ポイント

  • count が変わるたびに、新しいクロージャに基づいて動作してほしい。
  • 依存配列がなければ、古い count をずっと使ってしまう。

④ 親から渡された props の変化を監視したいとき

useEffect(() => {
  console.log("親からの user が変わった", user);
}, [user]);

ポイント

  • props の変化をトリガーに何かをしたい(ログを出す、状態を更新する、など)。

なぜ依存配列が重要か

1. useEffect は副作用の実行タイミングを制御する仕組み

React のコンポーネントは何度も再レンダリングされますが、副作用(例:API通信、DOM操作、イベント登録)はそのたびに無条件で行うと無駄やバグの原因になります。

そのため、 必要なときだけ副作用を実行する必要があり、その条件を指定するのが「依存配列」です。

2. 依存配列 = 「この値が変わったら再実行していいよ」リスト

React は依存配列を監視して、

  • 変化があれば useEffect を再実行
  • 変化がなければスキップ

という最適化を行います。

依存配列を省略するとどうなるか

useEffect(() => {
  // 毎回実行される
  doSomething();
});

デメリット 1:パフォーマンスの悪化

  • レンダリングごとに副作用を実行してしまい、不要な処理が増える
  • たとえば API リクエストを毎回送る → 無駄な通信・サーバー負荷増

デメリット 2:状態のループ・無限再実行のリスク

useEffect(() => {
  setValue("abc");
});
  • setValue → 再レンダリング → useEffect → また setValue...
  • 無限ループが発生する可能性あり(特に依存関係を持つ場合)

デメリット 3:古い値のまま処理される

useEffect(() => {
  console.log(count);  // 最新の count を見てない可能性がある
}, []);  // count に依存しているのに空配列にしてしまった
  • 本来 count が変わるたびに実行されるべき処理が、
  • 空配列により「初回しか動かない」=状態が変わっても処理されない

難しい論点と設計ポイント

1. 関数・オブジェクト・配列の参照変化問題

問題

依存配列に関数やオブジェクト・配列など「毎回新しく生成される値」を入れると、毎回再実行される

useEffect(() => {
  doSomething();
}, [someFunc]); // someFunc は useEffect 外で毎回新しく定義されてると NG

解決

  • useCallback / useMemo でメモ化して、参照が変わらないようにする。
const stableFunc = useCallback(() => {
  ...
}, [deps]);

useEffect(() => {
  stableFunc();
}, [stableFunc]);
  • React は浅い比較しかしないので、再実行を避けるには同一参照を保つ工夫が必要。

2. useEffect のクロージャトラップ

問題

依存配列に入れていない変数の古い値を useEffect がキャプチャして使ってしまう。

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // この中の count は「この useEffect が定義された時点の count」
    console.log(count);
  }, []);
}

上記の useEffect の中の count は、初回の count=0 のときのスナップショットを「クロージャとしてキャプチャ」しています。

つまり、useEffect の中だからといって、自動的に最新の値が使われるとは限りません。

解決

  • 最新の値を使いたい場合は ref を使う。
const countRef = useRef(count);

// count が変わるたびに ref を更新
useEffect(() => {
  countRef.current = count;
}, [count]);

useEffect(() => {
  const id = setInterval(() => {
    console.log(countRef.current);  // 毎回最新の count
  }, 1000);
  return () => clearInterval(id);
}, []);  // setInterval 自体は1回だけでOK

3. 副作用のキャンセルとクリーンアップ

シーン

非同期通信やタイマーを使っていて、コンポーネントのアンマウント時や再実行時に前の処理を無効化したい

useEffect(() => {
  let isMounted = true;
  fetch("/api").then((res) => {
    if (isMounted) setData(res);
  });
  return () => {
    isMounted = false;
  };
}, []);

または AbortController を使う

useEffect(() => {
  const controller = new AbortController();
  fetch("/api", { signal: controller.signal }).catch((e) => {
    if (e.name !== "AbortError") throw e;
  });
  return () => controller.abort();
}, []);

4. 再実行を意図的に抑制・強制したいケース

条件によって実行したりしなかったりするような制御も可能

useEffect(() => {
  if (!shouldRun) return;
  doSomething();
}, [shouldRun, triggerKey]);

→ 外部の triggerKey などをトグルして意図的に再実行させる設計。


useEffect の依存配列まとめ

  • useEffect は副作用を定義するフックで、いつ実行するかを依存配列で制御する。
  • 依存配列に値を入れると、その値が変わったときだけ実行される。
  • 省略すると毎回実行されるため、パフォーマンス悪化や無限ループの原因になる。
  • オブジェクト・配列・関数は参照が毎回変わるので、useMemouseCallback でメモ化して安定させる。
  • 最新の状態を非同期内などで参照したい場合は、useRef で最新値を保持するのが安全。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?