React 的渲染流程是一个复杂但高效的过程,它的核心目标是将组件的状态和属性变化反映到用户界面上,同时尽可能优化性能。React 的渲染流程可以分为以下几个阶段:初始化渲染、更新渲染 和 组件卸载。其中,初始化和更新渲染流程是最核心的部分。
React 渲染流程:总体结构
-
初始化阶段
- 初次渲染时,构建组件树并挂载到页面上。
-
更新阶段
- 组件状态或属性变化时,根据新旧状态重新渲染需要更新的部分。
-
卸载阶段
- 组件被移除时执行清理操作。
渲染流程细分为以下三个主要步骤:
阶段 | 子步骤 | 关键方法/概念 |
---|---|---|
Render 阶段 | 1. 构建或更新 Fiber 树 | beginWork ,reconcileChildren |
2. 调和新旧 Fiber 树,生成更新计划 | Diffing ,effectTag | |
Commit 阶段 | 3. 应用更新到真实 DOM | commitWork ,commitPlacement |
4. 执行副作用(useEffect 等) | commitEffectListMount | |
Cleanup 阶段 | 5. 清理无效的副作用 | useEffect 清理函数 |
详细的渲染流程
1. Render 阶段:生成 Fiber 树(可中断)
Render 阶段的目标是计算出需要更新的内容,生成或更新 Fiber 树(React 内部的数据结构),并标记需要更新的部分。
步骤:
-
开始渲染(
beginWork
方法) :- React 从根节点开始调用
beginWork
方法,根据组件类型(类组件、函数组件、DOM 节点等)处理 Fiber 节点。 - 如果组件有子组件,递归调用
beginWork
,生成或更新子节点的 Fiber 树。
- React 从根节点开始调用
-
协调(Reconciliation) :
- 使用 Diffing 算法 比较新旧 Fiber 树,找出需要更新的部分。
- 给需要更新的 Fiber 节点添加
effectTag
,标记更新类型(如UPDATE
、PLACEMENT
、DELETION
)。
-
完成渲染(
completeWork
方法) :- React 自底向上调用
completeWork
,将子组件的更新信息汇总到父组件。 - 生成一个包含所有更新计划的「副作用列表」(Effect List)。
- React 自底向上调用
特点:
- 可被中断:支持异步渲染,允许在高优先级任务(如用户输入)到来时暂停渲染并恢复。
2. Commit 阶段:应用更新到真实 DOM(不可中断)
Commit 阶段的目标是将 Render 阶段生成的更新计划应用到真实的 DOM,并执行副作用。这一阶段是同步的,不能被中断。
步骤:
-
DOM 更新:
-
根据 Fiber 节点的
effectTag
,执行对应的 DOM 操作:PLACEMENT
:插入新的节点到 DOM。UPDATE
:更新节点的属性。DELETION
:从 DOM 中移除节点。
-
-
执行生命周期方法:
- 调用类组件的生命周期方法(如
componentDidMount
、componentDidUpdate
)。 - 触发
useEffect
的副作用回调。
- 调用类组件的生命周期方法(如
-
清理无效的副作用:
- 调用
useEffect
的返回清理函数,移除不再需要的副作用。
- 调用
3. Cleanup 阶段:清理工作
当组件从页面中移除或状态更新时,React 会在适当时机清理无效的资源和副作用。
步骤:
- 调用组件的
componentWillUnmount
方法(类组件)。 - 执行
useEffect
返回的清理函数。 - 释放与 Fiber 节点相关的内存资源。
React 渲染流程图
1. Render 阶段(可中断)
-------------------------------------------
(1) 构建或更新 Fiber 树
(2) 比较新旧 Fiber 树(Diffing)
(3) 标记需要更新的节点(effectTag)
-------------------------------------------
2. Commit 阶段(不可中断)
-------------------------------------------
(4) 应用更新计划到 DOM
(5) 执行副作用(生命周期方法、useEffect)
-------------------------------------------
3. Cleanup 阶段
-------------------------------------------
(6) 清理副作用和资源
React 渲染优化点
-
异步渲染和中断(React 18 并发特性) :
- Render 阶段可以被时间切片中断,以响应高优先级任务。
- React 使用调度器控制任务优先级。
-
Diffing 算法优化:
- 使用 key 标记列表中的唯一项,避免不必要的 DOM 重排。
- 按需更新:只更新标记为
effectTag
的 Fiber 节点。
-
Hooks 副作用清理:
- React 会确保在重新渲染或组件卸载时清理无效的副作用。
-
React.memo 和 React.PureComponent:
- 减少不必要的重新渲染。
代码示例:渲染流程解析
function App() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("Effect: Component rendered");
return () => console.log("Cleanup: Component will re-render");
}, [count]);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
流程解析
-
Render 阶段:
setCount
更新状态,触发组件重新渲染。- React 创建新 Fiber 树并比较新旧树。
- 标记
<p>
节点的effectTag
为UPDATE
。
-
Commit 阶段:
- 更新 DOM,将新的
count
值反映到<p>
节点。 - 执行
useEffect
回调,输出"Effect: Component rendered"
。
- 更新 DOM,将新的
-
Cleanup 阶段:
- 在下一次渲染之前,执行返回的清理函数,输出
"Cleanup: Component will re-render"
。
- 在下一次渲染之前,执行返回的清理函数,输出
React 的渲染流程通过 Render 和 Commit 阶段分离,提高了性能和灵活性。对于开发者来说,理解这些阶段的职责有助于编写更高效和优化的代码。