第3回
useEffectの依存配列:正しい指定方法とよくある落とし穴
useEffectの依存配列を徹底解説。空配列・値の指定・オブジェクトや関数を依存配列に含めたときの問題と解決策、exhaustive-depsルールの使い方を実例付きで説明します。
制作・運営:React Guide 運営者
依存配列は「読んでいる値」の一覧
Effectのsetupが参照するprops・state・コンポーネント内の変数や関数を依存に含めます。実行頻度の希望だけで依存を選ぶと、古い値を使い続ける原因になります。
モジュールの定数や、そのコンポーネントのuseStateが返したsetterなど、変化しないと分かる値は省略できる場合があります。setterを依存に含めても誤りではありません。親からpropsで受け取った関数は、setterのような名前でも自動的に省略できません。
同じ数字でも別オブジェクト
Object.is(10, 10) // true
Object.is({ limit: 10 }, { limit: 10 }) // falseReactは依存値をそれぞれ 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を変えるなどの経路がなければ、無限には続きません。
修正の順番
- 外部との同期が必要かを確認します。表示用の計算ならレンダー中へ移します。
- Effectだけが使うオブジェクトや関数は、Effect内で作れるか確認します。
- 実際に読む
limitやroomIdなどを依存に含めます。 - 他の場所でも同じ関数を必要とする場合にuseCallbackを検討します。依存を省略して古い値を固定しないようにします。
useMemo/useCallbackは最適化です。キャッシュが破棄されても正しく再接続できるEffectにしてください。
リンタと合わせて確認する
react-hooks/exhaustive-deps の警告は、使う値と依存の不一致を見つける手掛かりです。設定方法は Hooksのルールを参照してください。警告を無効化する前に、同期の目的と値の作成場所を確認します。
修正後は「無操作で処理が増え続けない」と「limitを変えれば必要な処理が動く」の両方を確認します。詳しい不具合の診断は 無限ループの原因別修正へ。
同じテーマの教材
【解決】useEffectでasync/awaitを使う正しい方法と注意点
useEffectに直接asyncを付けてはいけない理由と、正しいasync/awaitの書き方を解説。内部関数として定義する方法・クリーンアップとの組み合わせ・競合状態(race condition)の防ぎ方まで実例付きで説明します。
useEffectのクリーンアップ:メモリリークを防ぐ正しい実装
useEffectのクリーンアップ関数を解説します。タイマー・イベントリスナー・非同期処理のキャンセルなど、アンマウント時に必要なクリーンアップの実装方法をメモリリーク防止の観点から学べます。
useEffectの基本:外部との同期・依存配列・クリーンアップ
ページタイトルを同期する完成サンプルでuseEffectの役割を学びます。イベント処理との違い、依存配列、クリーンアップ、StrictModeでの確認方法を解説します。