最近の事情を踏まえたReactのレンダリングってどんな流れだったっけ……?と思ったので、ざっくり整理してみます。
あまり細かい部分には踏み込まない&すべてを網羅しているわけではありませんので、そこはご容赦ください。
Reactの画面更新は3段階で考える
Reactが画面を更新する流れは、次の3段階に分けて考えられます。
順番に見ていきます。
1. Trigger:レンダリングのきっかけ
最初に、Reactがレンダリングを始めるきっかけが発生します。
主なきっかけは次の2つです。
- アプリの初回表示
- stateの更新
次のカウンターコンポーネントを考えてみます。
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount((current) => current + 1)}>
count: {count}
</button>
);
}
ボタンを押すと、setCountによって次のレンダリングが予約されます。
ただし、setCountが直接DOMを書き換えているわけではありません。
Reactに対して「stateが変わったので、次のUIを計算してください」と伝えているイメージです。
2. Render:次のUIを計算する
更新がトリガーされると、Reactはコンポーネント関数を呼び出します。
export function Counter() {
console.log('render');
// ...
}
このコンソールログが実行されるのが、Renderフェーズです。
Reactは現在のpropsやstateを使い、コンポーネントがどのようなUIを返すのかを計算します。
注意したいのは、RenderはDOMの更新そのものではないという点です。
Render
= コンポーネントを呼び出し、次に表示すべきUIを計算すること
コンポーネント関数が呼ばれた時点では、まだブラウザのDOMが変更されたとは限りません。
Renderは純粋である必要がある
Renderフェーズでは、同じpropsとstateを受け取ったら、同じ結果を返すようにコンポーネントを書くのが基本です。
そのため、コンポーネント関数の中で次のような副作用を起こすのは避けます。
- propsを直接書き換える
- コンポーネント外の変数を変更する
- APIリクエストを実行する
- DOMを直接操作する
開発環境で<StrictMode>が有効な場合、Reactは意図しない副作用を見つけやすくするために、コンポーネント関数を追加でもう一度呼び出すことがあります。
Renderは何度呼ばれても同じ結果になるはず
↓
開発時に追加で呼び出して、純粋でない処理を見つける
そのためコンソールログが2回表示されることがありますが、これは開発時だけのチェックです。
React CompilerはRenderをどう変えるのか
Renderフェーズでは、コンポーネント関数を呼び出して次のUIを計算します。
これまでReactでは、不要な再計算や子コンポーネントの再レンダリングを抑えるために、次の機能を手動で使うことがありました。
React.memouseMemouseCallback
React 19の世代で本格的に使いやすくなったReact Compilerは、コードをビルド時に解析し、こうしたメモ化の一部を自動化します。
React Compilerを使っても、Trigger・Render・Commitという流れが別物になるわけではありません。
Renderフェーズで繰り返される計算や、変更のないコンポーネントの再レンダリングを減らす仕組み、と考えると分かりやすそうです。
ただし、次の点には注意が必要です。
- React Compilerは導入・設定した環境で機能する
- React 19で~と書いたが、追加設定によりReact 17・18でも利用できる
-
useMemoやuseCallbackが完全に不要になったわけではない - 既存のメモ化を理由なく一括削除するものではない
以前より手動で最適化する場面は減りますが、まず計測し、必要な場合に最適化するという考え方は変わりません。
3. Commit:必要な変更をDOMへ反映する
Renderフェーズで次のUIが決まると、Reactはその結果をDOMへ反映します。
これがCommitフェーズです。
初回表示では必要なDOMを作成し、再レンダリング時には前回の結果と比べて、必要な部分だけを更新します。
前回: count: 0
今回: count: 1
変更が必要なテキストだけをDOMへ反映する
つまり、コンポーネント関数が再び呼ばれても、そのコンポーネントに対応するDOMがすべて作り直されるわけではありません。
次の例では、Parentのstateが更新されると、子であるGreetingも再レンダリングされます。
import { useState } from 'react';
function Greeting({ name }: { name: string }) {
console.log('Greeting render');
return <p>こんにちは、{name}さん</p>;
}
export function Parent() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount((current) => current + 1)}>
count: {count}
</button>
<Greeting name="山田" />
</>
);
}
ボタンを押しても、Greetingが返す内容は変わりません。
Greetingの関数は呼ばれますが、前回と結果が同じであれば、Reactは対応するDOMを変更しません。
整理すると、次のようになります。
再レンダリング
= コンポーネント関数を再び呼び出し、次のUIを計算すること
DOM更新
= 計算結果に変更があった部分をブラウザへ反映すること
再レンダリングとDOM更新は、同じものではありません。
2026年7月時点で押さえておきたいレンダリング関連機能
基本のTrigger・Render・Commitは変わりませんが、React 19.2ではレンダリングの扱い方や観測方法も少しずつ進化しています。
Activity:非表示のUIを捨てずに残す
React 19.2では、UIを非表示にしながら内部stateを保持できる<Activity>が追加されました。
import { Activity } from 'react';
<Activity mode={isOpen ? 'visible' : 'hidden'}>
<Sidebar />
</Activity>
hiddenにした子要素は画面上では非表示になりますが、内部stateは保持されます。
非表示中のEffectは停止され、更新は表示中のUIより低い優先度で処理されます。
表示中のUI
→ ユーザーが今見ているので優先してRender
非表示のUI
→ stateは残しつつ、余裕があるときに処理
すべての更新を同じ優先度で扱うのではなく、現在見えているUIを優先できる仕組みです。
Performance Tracks:レンダリングを観測する
React 19.2では、ブラウザのPerformanceパネルでReact固有の処理を確認できるReact Performance Tracksも追加されました。
Schedulerやコンポーネント、Effectなどの動きを、ネットワーク通信やJavaScript実行と同じ時間軸で確認できます。
例えば表示までに時間がかかるといった問題が出たとき、再レンダリングが発生したことだけを見て問題と判断するのではなく、
- どの更新に時間がかかったのか
- ユーザー操作をブロックしているか
- 実際に体感できる遅さがあるか
を計測して判断しやすくなっています。
まとめ
Reactの画面更新は、次の3段階で考えられます。
Trigger
更新のきっかけが発生する
Render
コンポーネントを呼び出して、次のUIを計算する
Commit
必要な変更をDOMへ反映する
特に覚えておきたいのは、次の点です。
RenderはDOM更新そのものではない
再レンダリングされても、DOMが更新されるとは限らない
Renderは何度呼ばれても問題がないように純粋に書く
2026年時点では、React Compilerによる自動メモ化や、<Activity>による非表示UIの管理、Performance Tracksによる観測も利用できるようになっています。
とはいえ、基本となるTrigger・Render・Commitの流れは変わりません。
新しい機能も、この基本のどこを助けるものなのかと考えると、少し整理しやすくなるかもしれませんね。