Reactのコードを書いていて、「なぜこのタイミングで処理が走るのか」「なぜuseEffectを使わないとバグるのか」と頭を抱えたことはないだろうか?
多くのエンジニアが「仮想DOMが速い」という表面的な知識だけで止まっている。しかし、Reactを使いこなすということは、Reactというエンジンが裏側でどう動いているか、その「呼吸」を理解することに他ならない。
今日は、Reactの心臓部である「Renderフェーズ」と「Commitフェーズ」について、現場の視点から徹底的に解剖しよう。
—
1. Renderフェーズ:Reactの「脳内シミュレーション」
Renderフェーズは、Reactがコンポーネントを呼び出し、新しい仮想DOMツリーを構築するフェーズだ。
重要なのは、このフェーズは「純粋」であるべきということだ。Reactはパフォーマンスのために、このフェーズを中断したり、再試行したり、あるいは裏でこっそり並列処理したりする可能性がある。もしここでAPIを叩いたり、`console.log`以外の副作用(グローバル変数の書き換えなど)を実行したりすると、予測不能なバグの温床になる。
- 役割: コンポーネントを評価し、前回との差分(Reconciliation)を計算する。
- 鉄則: 「副作用」は一切持ち込まない。計算結果を返すことだけに集中せよ。
2. Commitフェーズ:現実世界への「外科手術」
計算が終わると、ReactはCommitフェーズに移行する。ここで初めて、計算された差分が実際のブラウザDOMへと反映される。
ブラウザにとってDOM操作は非常に重い処理だ。だからこそ、Reactは計算をRenderフェーズで済ませ、Commitフェーズで「最小限のDOM操作」を一気に流し込む。このタイミングで、`useLayoutEffect`(同期的な副作用)や`useEffect`(非同期的な副作用)が走り出すことになる。
—
3. 実践:副作用のタイミングを制御する
「画面がチラつく」というバグに遭遇したことはないか? それは多くの場合、副作用を走らせる場所を間違えているからだ。以下のコードを見てほしい。
import React, { useState, useLayoutEffect, useEffect, useRef } from ‘react’;
const DomSyncComponent = () => {
const [width, setWidth] = useState(0);
const elementRef = useRef
// useLayoutEffectは「Commitフェーズ直後、ブラウザが画面を描画する前」に実行される
// DOMのサイズを取得して即座に画面に反映させたい時に使う
useLayoutEffect(() => {
if (elementRef.current) {
const { offsetWidth } = elementRef.current;
setWidth(offsetWidth);
}
}, []);
// useEffectは「ブラウザが描画を終えた後」に実行される
// 画面の見た目に直結しない「API通信」や「ログ送信」などを行うべき場所
useEffect(() => {
console.log(“描画完了!副作用を実行します。”);
}, []);
return (
);
};
なぜこの使い分けが重要なのか?
1. useLayoutEffect: 描画前にDOMをいじれる。`getBoundingClientRect()`でサイズを測ってから表示を切り替えるような「見た目の同期」が必要な時はここ。ただし、重い処理を書くとブラウザがフリーズするから注意が必要だ。
2. useEffect: 描画後に実行される。ユーザーは既に画面を見ているため、多少重い処理をしても体感速度には影響しにくい。「画面を見せた後に裏でやるべきこと」は全てここへ放り込め。
—
現場のシニアからのアドバイス:意識すべき「意識の転換」
中級エンジニアが陥りやすい罠は、「Renderフェーズでやっても良いこと」と「Commitフェーズでやるべきこと」の境界線が曖昧なことだ。
例えば、`useState`のセッターをコンポーネントのトップレベルで無防備に呼べば、無限ループが発生する。それは、ReactがRenderフェーズで「これは更新が必要だ」と判断し、再度Renderをやり直すからだ。
現場で勝つためのチェックリスト:
- [ ] コンポーネントの中に`fetch`や`localStorage`の操作を直接書いていないか?(→ それは`useEffect`へ移動せよ)
- [ ] 画面のチラつきはないか?(→ `useLayoutEffect`への切り替えを検討せよ)
- [ ] 複雑な計算を毎回Renderで実行していないか?(→ `useMemo`で計算結果をキャッシュせよ)
Reactは「書く」ものではなく、「振る舞いを定義する」ものだ。RenderとCommit、この二つのフェーズの呼吸を合わせることで、君たちのアプリケーションは驚くほど軽快で、堅牢なものになるはずだ。
次は、この知識を武器に、Reactのレンダリング最適化(`React.memo`や`useCallback`)の深淵に飛び込んでみよう。何か詰まったら、いつでも聞いてくれ。現場からは以上だ。

コメント