できない.dev

Stale Closure

別名: 古いクロージャ / stale closure

関数が古いレンダリング時点の変数を捕捉したまま実行され、最新値が読めなくなる現象。React の useEffect や setInterval で頻発し、関数型 setState や useRef で回避する。

#react#hooks#useeffect#closure#stale-closure

公開:

定義

Stale Closure(古いクロージャ)は、関数が定義時点のスコープで捕捉した変数の「古いスナップショット」を保持し続け、後で呼び出されたときに最新値ではなく古い値を読んでしまう現象。React フックでは useEffect 内の setInterval や非同期コールバックが、最初のレンダリング時の state を掴んだままになる典型例として知られる。

詳細

React の関数コンポーネントは毎レンダリングで本体が再実行されるため、その時点の props / state を捕捉した新しいクロージャが毎回作られる。依存配列 に該当値を入れ忘れると、エフェクト内の関数は初回レンダー時のクロージャを保持し続け、setCount(count + 1) のような書き方では count がいつまでも 0 のままという挙動になる。
回避策は (1) 関数型 setState setCount(c => c + 1) で前回値から派生させる、(2) useRef に最新値をミラーする、(3) 依存配列に必要な値を正しく列挙する、の 3 つ。

よくある誤解

「変数が破壊された」「state が壊れた」と思われがちだが、JavaScript のクロージャ仕様どおりに動いているだけで React 固有のバグではない。
Strict Mode の二重実行とも独立した現象で、本番でも当然発生する。

関連

useEffect の無限ループsetState しても画面が更新されない 問題の根本原因として頻出する。