第3回

useEffectの依存配列:正しい指定方法とよくある落とし穴

useEffectの依存配列を徹底解説。空配列・値の指定・オブジェクトや関数を依存配列に含めたときの問題と解決策、exhaustive-depsルールの使い方を実例付きで説明します。

·6分で読める

制作・運営:React Guide 運営者

依存配列は「読んでいる値」の一覧

Effectのsetupが参照するprops・state・コンポーネント内の変数や関数を依存に含めます。実行頻度の希望だけで依存を選ぶと、古い値を使い続ける原因になります。

モジュールの定数や、そのコンポーネントのuseStateが返したsetterなど、変化しないと分かる値は省略できる場合があります。setterを依存に含めても誤りではありません。親からpropsで受け取った関数は、setterのような名前でも自動的に省略できません。

同じ数字でも別オブジェクト

Object.is(10, 10) // true
Object.is({ limit: 10 }, { limit: 10 }) // false

Reactは依存値をそれぞれ Object.is で比較します。オブジェクトを依存に入れることは合法です。問題は、必要がないのに毎回作り直している場合です。

次は依存の違いをログで確かめる src/App.tsx の例です。外部APIは不要です。

import { useEffect, useState } from 'react'

export default function App() {
  const [limit, setLimit] = useState(10)
  const [note, setNote] = useState('')
  const options = { limit }

  useEffect(() => {
    console.log('object依存', options.limit)
  }, [options])

  useEffect(() => {
    const requestOptions = { limit }
    console.log('limit依存', requestOptions.limit)
  }, [limit])

  return (
    <main>
      <label>無関係なメモ<input value={note} onChange={e => setNote(e.target.value)} /></label>
      <button onClick={() => setLimit(n => n + 1)}>取得数:{limit}</button>
    </main>
  )
}

メモを入力すると options は新しくなるため、最初のEffectが再実行されます。limitは変わらないので2つ目は再実行されません。どちらもstateを更新していないため、この例では無限ループになりません。

初期ログはStrictModeで追加実行される場合があります。ここでは総回数ではなく、メモ入力後にどちらが増えるかを比べます。最初のEffectは比較用に意図的に不安定な依存を使っており、リンタが注意を出す場合があります。

無限ループには更新が戻る経路が必要

毎回作るoptionsが変わる
 → Effectが取得を始める
 → 結果を新しいオブジェクトとしてstateへ保存する
 → 再レンダーでoptionsを作り直す
 → 最初へ戻る

propsで受け取ったconfigが子自身のstate更新だけで新しくなるとは限りません。「configがあるから必ずループ」と判断せず、どこがその参照を作っているか追います。

関数も同じです。新しいコールバックを依存にして再実行されても、その実行が再び親のstateを変えるなどの経路がなければ、無限には続きません。

修正の順番

  1. 外部との同期が必要かを確認します。表示用の計算ならレンダー中へ移します。
  2. Effectだけが使うオブジェクトや関数は、Effect内で作れるか確認します。
  3. 実際に読む limit や roomId などを依存に含めます。
  4. 他の場所でも同じ関数を必要とする場合にuseCallbackを検討します。依存を省略して古い値を固定しないようにします。

useMemo/useCallbackは最適化です。キャッシュが破棄されても正しく再接続できるEffectにしてください。

リンタと合わせて確認する

react-hooks/exhaustive-deps の警告は、使う値と依存の不一致を見つける手掛かりです。設定方法は Hooksのルールを参照してください。警告を無効化する前に、同期の目的と値の作成場所を確認します。

修正後は「無操作で処理が増え続けない」と「limitを変えれば必要な処理が動く」の両方を確認します。詳しい不具合の診断は 無限ループの原因別修正へ。

React公式:Effectから不要な依存を取り除く、useEffectの比較方法

#react#hooks#useEffect#deps#intermediate