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 渲染都经历一个可预测的两阶段管道:渲染阶段和提交阶段。渲染阶段是纯粹且可中断的 — React 调用组件并构建新的 Fiber 树。提交阶段是同步且有副作用的 — React 将计算好的变更应用于真实 DOM 并触发 effects。
在构建时,像 <Button color='blue'>Click</Button> 这样的 JSX 变成 React.createElement(Button, '{' color: 'blue' '}', 'Click')。这产生一个普通的对象树 — 虚拟 DOM。
React 遍历 Fiber 树,调用每个组件函数(或 render() 方法)。这个阶段是纯粹的,不产生副作用 — 在并发模式下,React 可以随时中断并重启它。
React 将新输出与之前的 Fiber 树进行比较。它用 effect 标签标记每个 Fiber:Update、Placement(插入)或 Deletion。结果是一个 effect 列表 — 包含待处理工作的 Fiber 扁平列表。
React 在类组件上调用 getSnapshotBeforeUpdate() 并调度被动 effects。这个子阶段在任何 DOM 变更发生之前运行。
React 遍历 effect 列表并同步应用所有 DOM 变更:插入、更新、删除。此后,DOM 反映新的 UI。
useLayoutEffect 回调在 DOM 变更后、浏览器绘制前同步运行。然后浏览器绘制。最后,useEffect 回调在绘制后异步运行。
纯粹、无副作用的阶段,React 调用组件并构建新的 Fiber 树。在并发模式下可以被中断。
同步阶段,React 应用 DOM 变更并触发生命周期方法/effects。不能被中断。
在 DOM 变更后、浏览器绘制前同步触发。用于 DOM 测量,会阻塞绘制。
在浏览器绘制后异步触发。不阻塞绘制 — 适合订阅、数据获取和非视觉副作用。
在开发环境中,React 在渲染阶段渲染组件两次以检测副作用。只有一次提交发生。
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).