Reactで注意したい現象、Stale Closureとは?

Closureとは

Stale Closureについて説明する前に、Closureとは何でしょうか?
Closure(クロージャ)は、関数が定義された時点の周囲の状態(レキシカルスコープ)を「記憶」するJavaScriptの機能です。簡単に言うと、「関数が作成された時点での変数や状態を保持し続ける機能」と言えます。

詳しくはこちらをご覧ください。

Stale Closureとは?

Stale Closureは、クロージャによって関数が定義された時点の変数や状態のスナップショットを「記憶」し続け、後にそれらが更新されても古い値を参照し続ける現象を指します。

Reactでは、コンポーネントのStateやpropsが更新されるとコンポーネントが再レンダリングされますが、非同期処理(setTimeoutやsetInterval、あるいは外部APIからのデータ取得など)の中で参照されるStateやpropsの値は、その非同期処理が開始された時点の値に「固定」されます。

例として、ボンバーマンゲームを考えてみましょう。このゲームでは、プレイヤーが「ボムを渡す」操作を行うことができ、ゲーム開始後一定時間が経過すると、ボムを持っているプレイヤーが爆発の対象となります。

以下の手順で試してみてください。

  1. ゲームスタートボタンをクリック
  2. ボムを誰かに渡す

See the Pen create-react-app by 山下琳太郎 (@bzoqecbt-the-looper) on CodePen.

磯野くんがいくら他の人にボムを渡しても、必ず磯野くんで爆発してしまいますね。
正確にいうと、ボムを渡しているつもりが渡せていないのです。

setTimeoutのコールバック関数は、その関数が定義された時点でのbomberステートの値を「記憶」します。そのため、setTimeoutが設定された後にbomberステートが更新されても、コールバック関数が実際に実行される5秒後には、setTimeoutが設定された時点のbomberの値、すなわち磯野くんのまま固定されます。よって、磯野くんで必ず爆発します。

const handleGameStart = () => {
    setStatusMessage("ゲームが開始されました...");
    setTimeout(() => {
        alert(`${bomber}で爆発💣!`);
    }, 5000);
};

参考

9. 痺れ、瞬き、眠りを妨げる Stale Closure

Reactの技術的な疑問や課題について、お気軽にご連絡ください。