ウェブエンジニア問題集
第3章

Reactのレンダー・マウント・再レンダー・アンマウントの違い

8
この章の目次開く

Reactのフック(useStateuseEffect など)を正しく使うには、レンダーマウントの違いを理解しておく必要があります。 この2つは混同されやすいですが、指しているものがまったく異なります。

学習者学習者

「レンダー」と「マウント」って同じ意味じゃないの…?なんとなくで使ってたけど、違うものなの?


まず結論

Reactで「レンダーされた」と「マウントされた」は同じ意味ではありません。レンダーは関数が呼ばれてJSXを計算すること、マウントはその結果が初めてDOMに入ることです。

状況起きること
初めて画面に出る初回レンダー → DOMに挿入 → マウント後の useEffect
stateが変わる再レンダー → DOMの差分更新
propsが変わる再レンダー → DOMの差分更新
条件分岐で消えるアンマウント → useEffect のクリーンアップ

この区別が分かると、useEffect(..., []) がなぜ初回だけ実行されるのか、state更新でなぜ関数全体が再実行されるのかを説明できます。


4つの用語

用語意味
レンダーコンポーネントの関数が呼ばれてUIを計算すること
マウントコンポーネントが最初にDOMに追加されること
再レンダー2回目以降のレンダー
アンマウントコンポーネントがDOMから取り除かれること

レンダーとは

レンダーとは、Reactがコンポーネント関数を呼び出してUIの計算結果(JSX)を得ることです。

function Greeting({ name }: { name: string }) {
  // この関数が呼ばれること自体が「レンダー」
  return <p>こんにちは、{name}さん</p>;
}
tsx

レンダーのたびに関数全体が再実行されます。constlet で定義したローカル変数も、すべて作り直されます。

レンダーが起きるタイミング

  • 自身のstateが変わったときuseState の更新関数を呼んだとき)
  • 親コンポーネントが再レンダーされたとき(子も連動して再実行される)
function Counter() {
  const [count, setCount] = useState(0);
 
  console.log('rendered'); // ボタンを押すたびにここが実行される
 
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}
tsx

ボタンを押すたびに Counter 関数が再実行され、console.log('rendered') が出力されます。


マウントとは

マウントとは、コンポーネントが初めてDOMに挿入されることです。ライフサイクル中に1回だけ起きます。

マウントは「初回レンダー + DOMへの挿入」の組み合わせです。

マウント = 初回レンダー + DOMへの挿入

コンポーネントがページに表示されるとき、Reactは次の順序で処理します。

  1. コンポーネント関数を呼ぶ(= 初回レンダー)
  2. 戻り値のJSXをDOMに挿入(= マウント完了)
  3. useEffect のコールバックを実行(マウント後)

マウント = 画面リロード?

学習者学習者

マウントって、ページを再読み込みしたときのことでしょ?

これはよくある誤解です。画面リロードでマウントは起きますが、マウントは画面リロードだけで起きるものではありません

集合の包含関係で整理すると、次のようになります。

レンダー ⊃ マウント ⊃ 画面リロード
用語範囲
レンダーマウント + 再レンダーを含むすべて
マウント画面リロード + それ以外のマウントを含む
画面リロードマウントが起きる場面の1つにすぎない

マウントが起きる場面

画面リロードは、アプリ全体をゼロから作り直すため全コンポーネントがマウントされます。しかしマウントはそれだけではありません。

function App() {
  const [show, setShow] = useState(false);
 
  return (
    <>
      <button onClick={() => setShow(!show)}>toggle</button>
      {show && <Child />}
    </>
  );
}
tsx

この例では、ボタンを押して showtrue になるたびに <Child /> がDOMに追加され、マウントが起きます。false になればアンマウントされ、再び true になれば新しいインスタンスとして再びマウントされます。

ページを再読み込みしなくても、以下の場面でマウントは発生します。

  • 条件分岐で表示されたとき{show && <Component />}
  • ルート遷移で新しいページに移動したとき — 遷移先のコンポーネントがマウントされる
  • key が変わったとき — Reactが別のインスタンスとして作り直す

useEffectの依存配列との対応

学習者学習者

条件分岐で表示したコンポーネントも毎回マウントされるなら、useEffect(..., []) は何回も実行されるってこと?

先生先生

そのとおり。[] は「マウント時に1回だけ」だけど、コンポーネントが消えて再び表示されればまた新しいマウントだから、そのたびに実行されるよ。

どのパターンでもマウント時は必ず実行されるのが共通ルールです。

依存配列マウント時再レンダー時
[]実行される実行されない
[a, b]実行されるab が変わった時だけ実行
なし実行される毎回実行される

useEffect(..., []) が「1回だけ」と言われるのは、再レンダーでは実行されず、マウント時だけ実行されるためです。そして画面リロードに限らず、条件分岐やルート遷移でコンポーネントが表示されるたびにマウントは起きるため、[] でも複数回実行されることがあります。


再レンダーとアンマウント

ライフサイクルの全体像をひらめくイメージ

再レンダー — stateやpropsの変化をきっかけに、マウント後に繰り返されるレンダーです。DOMへの挿入は最初の1回だけで、再レンダーではReactが差分だけを更新します。

アンマウント — コンポーネントが画面から取り除かれることです。条件分岐({show && <Component />})やルート遷移などで発生します。アンマウント時に useEffect のクリーンアップ関数が実行されます。


全体の流れ


コードで確認する

function Counter() {
  const [count, setCount] = useState(0);
 
  // レンダーのたびにここが実行される(初回 + 再レンダー)
  console.log('rendered');
 
  useEffect(() => {
    // マウント時に1回だけ実行される
    console.log('mounted');
 
    return () => {
      // アンマウント時に実行される
      console.log('unmounted');
    };
  }, []);
 
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}
tsx

ボタンを3回押したときのコンソール出力:

rendered   ← 初回レンダー(マウント時)
mounted    ← マウント完了後
rendered   ← 再レンダー(1回目)
rendered   ← 再レンダー(2回目)
rendered   ← 再レンダー(3回目)

rendered は計4回(初回 + 3回)、mounted は1回だけです。コンポーネントが画面から消えるとき(アンマウント)に unmounted が出力されます。


useEffectとの関係

この違いが useEffect の依存配列と直接つながります。

// 依存配列なし → 毎レンダー後に実行(再レンダーのたびにも走る)
useEffect(() => {
  console.log('every render');
});
 
// 空配列 → マウント時に1回だけ実行
useEffect(() => {
  console.log('mount only');
}, []);
tsx

「依存配列なし」と「空配列 []」は見た目が似ていますが、前者は毎レンダー後に実行され、後者はマウント時のみです。この違いはレンダーとマウントの概念を理解していれば自然に納得できます。

詳しくは「useEffect — 副作用と外部同期」で解説しています。


まとめ

  • レンダー — コンポーネント関数の実行。stateやpropsの変化のたびに何度でも起きる。
  • マウント — 初回レンダー + DOMへの挿入。ライフサイクル中に1回だけ。
  • 再レンダー — 2回目以降のレンダー。DOMは差分だけ更新される。
  • アンマウント — コンポーネントがDOMから取り除かれるとき。

useEffect(..., []) が「マウント時のみ」と言われるのは、空配列だと何にも反応しないため最初の1回しか実行されないからです。レンダーとマウントの違いを押さえると、フックの動作が一気にわかりやすくなります。


参考

Reactクイズに挑戦するレンダー、マウント、再レンダー、アンマウント、StrictModeやhydrationの考え方を4択クイズで確認しよう