853 字
4 分钟
React 的工作流程:Render 与 Commit

官方能给的:三步#

Render and Commit 把一次界面更新拆成三步:

  1. 触发。初次渲染调 root.render(),之后由 state 更新排入一次渲染。
  2. 渲染。React 调用你的组件,拿到描述界面的对象树。初次渲染从根组件开始;重渲染从「state 变了的那一个组件」开始,再递归到它返回的子组件。
  3. 提交。React 改 DOM。

两个阶段的分工是理解这套流程的关键:渲染阶段只算出要改什么,提交阶段才真的动 DOM。官方的原话是,重渲染时「应用必要的最小改动,而这些改动是渲染期间算出来的」。

一个能看出差别的例子#

function Clock({ time }) {
return (
<>
<h1>{time}</h1>
<input />
</>
)
}

父组件每秒给它一个新的 time。你在输入框里打字,字不会因为重渲染消失:

import { useState, useEffect } from 'react'
import Clock from './Clock.js'
function useTime() {
const [time, setTime] = useState(() => new Date())
useEffect(() => {
const id = setInterval(() => setTime(new Date()), 1000)
return () => clearInterval(id)
}, [])
return time
}
export default function App() {
const time = useTime()
return <Clock time={time.toLocaleTimeString()} />
}

原因在提交阶段:React 只把 <h1> 的内容换成新时间。它看到 <input> 还在原来的位置,就没碰它,也没碰它的值。

官方把这条总结成一句:两次渲染结果相同,React 就不碰 DOM。

再往里,就是社区的重构了#

这里必须把来源说清楚,否则很容易把社区的解释当成官方规范。

react.dev 没有 Fiber 的文档,也没有调度器或 diff 算法的文档。 你在这几个词上能查到的权威来源只有一份:Andrew Clark 的 react-fiber-architecture。他在 README 开头写的是「我不在 React 团队,不代表任何权威,这不是官方文档」,并自述这份文档「远未完成」;仓库在 2024 年 7 月之后也没有再更新。

社区普遍引用的那套重构是:

  • 把元素树转换成一棵由 Fiber 节点组成的树,每个节点是最小工作单元,用 child、sibling、return 三个指针串起来,这样遍历可以随时中断、之后再恢复。
  • 有一个调度器按优先级把工作切成小段推进,遍历时对每个节点调用处理函数。
  • 渲染阶段可中断,提交阶段不可中断。
  • 提交完成后切换双缓冲指针,把工作树变成当前树。

把它当成「社区对内部实现的解释」来读,它很有用。它解释得了一个现象:为什么渲染能被用户输入打断。但字段名、函数名、时间片长度这些细节会随版本变,写进文章时要注明年代与来源。

官方能查到的只有行为级描述。例如 useTransition 那一页说的是「过渡更新可被打断」,不讲内部怎么实现。

那张「5ms 时间片、借鉴 requestIdleCallback」的说法属于上面这一层,出自第三方分析与旧演讲。要用这个数字,就一并写出处。

React 工作流程图

这张图是本站作者按上述社区模型画的,不是官方图。

和相邻几篇的关系#

元素是什么,见 虚拟 DOM 与 React 元素;JSX 怎么变成函数调用,见 Babel 与 SWC;可中断的行为面,见 useTransition 与 useDeferredValue;Fiber 那一层的细节,见 React 的 Fiber 架构。

第 36 篇,共 39 篇本文属于系列

React 的工作流程:Render 与 Commit
https://www.choria.top/posts/react-library/
作者
乔瑞雅·克林威尔
发布于
2026-05-01
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 191 天

部分内容可能已过时