react fiber与事件循环
在学习react fiber与事件循环的过程中,我一直有这么一个困扰,那就是单独学习的时候,我好像都理解,但是我的脑袋里面没有一条清晰的时间线,到底react的任务从浏览器的视角来看到底是怎么执行,怎么渲染的,为此,我专门将react fiber与浏览器的事件循环放在一起进行学习,希望能够得到更清晰的学习结论。阅读本文,默认对事件循环和react fiber架构有一定了解。
事件循环
- 什么是浏览器的事件循环机制?
浏览器的事件循环是一套"协调机制",用来在单线程的 JavaScript 环境中,合理安排同步代码、异步任务和页面渲染的执行顺序。
- 为什么要有事件循环机制
js是单线程的(根本原因),但是它需要处理很多事情。
- 主线程的代码
- 处理dom
- 页面渲染
- 相应用户事件
很显然这些事情是需要一个"排队"的机制。
还有一些异步操作的存在:
- setTimeout
- 网络请求
- DOM 事件
- Promise
这些任务并不是js引擎去执行,而是web API,执行完后通知js的主线程,js再去处理这些回调。
结论:我们需要一套机制去处理上面所说的排队,去处理这些异步操作回调执行的时机。这就是事件循环。
那关于具体的事件循环的学习本文不涉及,这里只给出结论
一次事件循环 = 执行一个宏任务 → 清空所有微任务 → 进行页面渲染(可能)
- 先执行 一个宏任务
- 然后 执行所有微任务
- 然后浏览器 可能进行一次渲染
- 再进入下一轮循环
React fiber策略
- 什么是react fiber
简单来说,其实是 React 为了实现 可中断、可调度、可增量的渲染 所设计的内部机制。
我们来看一个简单的例子:
css
const elementTree = {
type: "div",
props:{
children: [
{
type: "span",
key: "span-a"
props: {
children: [
{
type: "TEXT_ELEMENT",
props: {
nodeValue: "aaaaa",
children: [],
},
}
]
}
},
{
type: "span",
key: "span-b"
props: {
children: [
{
type: "TEXT_ELEMENT",
props: {
nodeValue: "bbbbb",
children: [],
},
}
]
}
}
]
}
}
这是对应的fiber的链表图
fiber采用深度优先遍历,对每一个fiber节点都采用浏览器空闲时间进行计算,直到整棵树都计算完成,最后才提交渲染。
问题?
- 所谓的可中断,空闲时间,到底是指的浏览器事件循环的什么阶段
- fiber的计算,是在什么时候
一条完整时间线:React Fiber × 浏览器
场景:用户点击按钮 → React 更新 UI
🟦 阶段 1:浏览器的事
点击事件发生 `` ↓ `` 事件系统 `` ↓ ``宏任务(onClick)
🟩 阶段 2:React 开始工作(JS 阶段)
scss
onClick() {
setState(...)
}
React 做了什么:
- 不改 DOM
- 创建 Update
- 把 Update 挂到 Fiber
- 标记优先级(Lane)
- 请求调度(scheduleUpdateOnFiber)
👉 React 此时仍然完全在 JS 世界
🟨 阶段 3:Fiber Render 阶段(可中断)
React 在做:
- 构建新的 Fiber Tree
- Diff(Reconciliation)
- 计算 effect list
⚠️ 这个阶段:
- 不触碰 DOM
- 可以被中断
- 可能跨多帧
🟥 阶段 4:Commit 阶段(不可中断)
React 选择一个安全时机(通常靠近帧边界):
- 执行 DOM 操作
- 执行 layout effects
- 更新 refs
👉 这一刻, React 才真正影响浏览器渲染
🟪 阶段 5:浏览器渲染
rAF(浏览器) ``↓ Style ↓ Layout ↓ Paint ↓ Composite
React 和浏览器的分工一句话总结
React 决定"改什么"和"什么时候可以改", 浏览器决定"什么时候画"和"怎么画"。
React fiber实现的核心api
- MessageChannel:推进计算,立即排队下一个宏任务
- requestAnimationFrame:Commit 阶段对齐浏览器帧
Fiber 调度的最后一部分
MessageChannel 如何推进各个工作单元
在 React Fiber 中,整个组件树的更新被拆分成一个个 Fiber 单元(unit of work)。每个单元负责:
- 构建 Fiber 节点
- 进行 Diff 对比
- 计算 effect list
为什么 MessageChannel 被使用?
- Fiber 的 Render 阶段是可中断的,需要在主线程空闲时继续推进任务。
- MessageChannel 可以立即排队一个宏任务,在当前宏任务执行完成后尽快执行下一轮 Fiber 计算。
- 这意味着,React 不会一次性阻塞主线程去渲染整个组件树,而是切片执行,每次执行一小部分 Fiber 单元。
MessageChannel 任务所在队列:
- MessageChannel 的
onmessage回调属于宏任务队列。 - 宏任务执行完成后,微任务队列会被清空,然后浏览器判断是否渲染。
- 因此 Fiber 的 Render 阶段是宏任务中逐片推进的。
requestAnimationFrame 的使用时机
-
作用:与浏览器渲染帧同步,保证 Commit 阶段操作 DOM 时不掉帧。
-
使用时机:
- 当 Fiber Render 阶段完成或达到时间片限制时,Scheduler 会判断是否需要提交 DOM。
- Commit 阶段执行前,如果希望和浏览器下一帧对齐 ,就会通过
requestAnimationFrame调用 Commit 阶段回调。
-
为什么要使用 rAF:
- 避免直接提交 DOM 时阻塞渲染
- 保证渲染和浏览器帧同步,提高 UI 流畅度
- 避免计算阶段占用过长时间造成掉帧
结合事件循环的完整执行流程
以一次用户点击触发更新为例:

从上图我们可以简单的看出,react fiber的计算过程跨越了多次的事件循环,是否可中断的判断放在每一次的工作单元进行判断。且工作单元(nextUnitOfWork)的计算是放在宏任务中
总结:
-
MessageChannel 推进 Fiber 工作单元:
- 在宏任务队列中运行
- 逐片执行 Render 阶段
- 可以中断,分片执行以保证主线程空闲
-
requestAnimationFrame 使用:
- 对齐 Commit 阶段和浏览器渲染帧
- 避免掉帧,提高 UI 流畅度