👬

React なんで2回走るの?

2024/08/09に公開

ムーザルちゃんねるのムーです。今回は @zaru さんと「React の StrictMode で処理が2回走ること」について話しました。

React に入門してコードを書いていると「あれ、なんかここ 2 回実行されてない?」となる瞬間、あると思います。それについての話になります。

  • なんで2回走るのかわからない
  • StrictMode について知らない、知ってるけど2回走る意味がわからない
  • どこの処理が2回走るかわからない

という方に向けた動画となっています。

https://youtu.be/KYTEsaeFxCg

以下に簡単なサマリを書いておきます。

話したこと

  1. StrictMode があります
  2. なぜ StrictMode は 2 回走らせているのか?
  3. どこで 2 回走るの? (え、そこも2回走るの?)

StrictMode があります。

これによって、2 回走ります。
off にすれば、2 回走らなくなります。

ではなぜ 2 回走らせているのか?

React コンポーネントは、純粋関数であることが前提とされています。
純粋関数であれば、何度走っても結果は同じで、困らないはず。
複数回走って結果が変わる → 純粋じゃない → よくない(バグの可能性) → バグ早期発見!

結論: 純粋関数でない不具合の可能性のあるコードを発見しやすくするために、2 回走っています。

例: 純粋じゃない React component

let x = 0;

function NotPure() {
  x = x + 1;
  return <div>{x}</div>;
}
// コンポーネントの呼び出しタイミングで表示される内容が変わってしまう
<NotPure /> // 1
<NotPure /> // 2

2回走るものは何があるの?

  • component function
  • useState, setSomething, useMemo, useReduce に渡される関数
  • など

詳細はこちら
https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-double-rendering-in-development

なんで、setSomething のコールバックも2回走るの?

const [something, setSomething] = useState(null)

const handler = () => {
  setSomething(p => {
    return p + 1;
  }
}

このときの、setSomething の引数の関数も2回呼ばれます。

ですので、たとえば、以下のようなコードはよくないです。

// 経験値
const [exp, setExp] = React.useState(0);
// レベル
const [level, setLevel] = React.useState(1);

// よくない例
// 経験値が 100 を超えたら、レベルをひとつあげる
const ngGainExp = () => {
  setExp((prev) => {
    const newExp = prev + 20;
    if (newExp >= 100) {
      setLevel((prev) => prev + 1); // 副作用があり、pure じゃなくなっている
      return 0;
    }
    return newExp;
  });
};

上を修正するアイデアとして以下のような方法があります。あるいは、state をひとつにしてしまうのも良いでしょう。

(動画でも話しましたが、以下の例はいまいちかもしれない...)

// 修正した例
const gainExp = () => {
  const newExp = exp + 20;

  setExp(newExp >= 100 ? 0 : newExp);

  // setExp のコールバックから setLevel を引き離した
  if (newExp >= 100) {
    setLevel((prev) => prev + 1);
  }
};

ひとこと、まとめ

  • pure 関数で書こう!
ムーザルちゃんねる

Discussion