Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
JSXからピクセルまでの完全な旅 — レンダリングをトリガーして各フェーズを観察。
Reactがレンダリングするたびに、予測可能な2フェーズのパイプラインを経由します:レンダーフェーズとコミットフェーズです。レンダーフェーズは純粋で中断可能 — ReactはコンポーネントCallし新しいFiberツリーを構築します。コミットフェーズは同期的で副作用があります — Reactは計算された変更を実際のDOMに適用し、エフェクトを発火させます。
ビルド時に、<Button color='blue'>Click</Button>のようなJSXはReact.createElement(Button, '{' color: 'blue' '}', 'Click')になります。これはプレーンなオブジェクトツリー — 仮想DOMを生成します。
ReactはFiberツリーを走査して各コンポーネント関数(またはrender()メソッド)を呼び出します。このフェーズは純粋で副作用を生成しません — 並行モードではReactはいつでもこれを中断して再起動できます。
Reactは新しい出力を前のFiberツリーと比較します。各FiberにエフェクトタグをマークしますUpdate、Placement(挿入)、またはDeletion。結果は「エフェクトリスト」 — 保留中の作業を持つFiberのフラットなリストです。
ReactはクラスコンポーネントのgetSnapshotBeforeUpdate()を呼び出し、パッシブエフェクトをスケジュールします。このサブフェーズはDOMの変更が行われる前に実行されます。
ReactはエフェクトリストをWalkして、すべてのDOM変更を同期的に適用します:挿入、更新、削除。この後、DOMは新しいUIを反映します。
useLayoutEffectコールバックはDOM変更後、ブラウザが描画する前に同期的に実行されます。その後ブラウザが描画します。最後に、useEffectコールバックが描画後に非同期に実行されます。
Reactがコンポーネントを呼び出して新しいFiberツリーを構築する、純粋で副作用のないフェーズ。並行モードでは中断可能です。
ReactがDOMの変更を適用し、ライフサイクルメソッド・エフェクトを発火させる同期フェーズ。中断不可能です。
DOM変更後、ブラウザ描画前に同期的に発火します。DOM測定に使用します。描画をブロックします。
ブラウザ描画後に非同期で発火します。描画をブロックしません — サブスクリプション、フェッチ、非視覚的な副作用に最適です。
開発環境では、Reactは副作用を検出するためにレンダーフェーズでコンポーネントを2回レンダリングします。コミットは1回だけ行われます。
1function MyComponent() {2 // Fires: after every render, after browser paint (async)3 useEffect(() => {4 console.log('3. useEffect — DOM painted, async');5 return () => console.log('cleanup');6 });78 // Fires: after every render, before browser paint (sync)9 useLayoutEffect(() => {10 console.log('2. useLayoutEffect — DOM mutated, not painted');11 });1213 console.log('1. Render phase — pure, no side effects');14 return <div />;15}16// Order: Render → DOM mutation → useLayoutEffect → Paint → useEffect
パイプラインを理解することで、適切な場所にコードを配置できます。DOM測定はuseLayoutEffect(描画前)に入れる必要があります。データフェッチはuseEffect(描画後、非ブロッキング)に入れます。最初のレンダリング前に行う必要がある初期化はuseStateイニシャライザーまたはuseMemoに入れます。間違えると、視覚的なフラッシュ、レイアウトのジャンプ、またはレンダリングの欠落が発生します。
You're building a tooltip that positions itself relative to a target element. Using useEffect, the tooltip first renders at (0,0), then jumps to the correct position — causing a visible flash.
useEffect fires AFTER the browser paints. So the sequence is: Render (tooltip at 0,0) → Paint (user sees misplaced tooltip) → useEffect (measure & reposition) → Re-render → Paint (correct). The first paint shows the wrong position.
Switch to useLayoutEffect which fires AFTER DOM mutation but BEFORE paint. The sequence becomes: Render → DOM mutation → useLayoutEffect (measure & update) → Paint (correct position). The user never sees the wrong position because the browser hasn't painted yet.
Takeaway: Use useLayoutEffect for any DOM measurement or mutation that affects visual layout (tooltips, popovers, scroll position, animations). Use useEffect for everything else (data fetching, analytics, subscriptions).
You add console.log statements to debug when state changes propagate. The logs appear in an unexpected order: render logs appear before effect logs, and cleanup logs appear between them.
Without understanding the pipeline phases, developers assume effects run 'during' rendering. They place side effects in the render function body (causing bugs) or misread the log order and chase phantom timing issues.
The rendering pipeline has strict phases: Render (pure, produces VDOM) → Commit (DOM writes) → Layout Effects (sync, before paint) → Paint (browser) → Effects (async, after paint). Each console.log appears in pipeline order. Understanding this order lets you read debug output correctly and place code in the right phase.
Takeaway: The rendering pipeline is NOT a single step — it's 5 distinct phases with different timing guarantees. Most React bugs come from putting code in the wrong phase (e.g., side effects in render, DOM reads in useEffect instead of useLayoutEffect).