React 渲染机制速通:Render 树、Fiber 树与更新调度

面试官最常追问的 React 问题,绕不开两棵「树」:Render 树和 Fiber 树。别被名字吓到------这篇把更新从 setState 到 DOM 落地的完整链路拆开,配上流程图,讲完就能对上号。

读完你会知道:一次状态更新到底经历了什么,为什么页面不卡顿,以及虚拟列表、批量更新这些优化手段怎么和 Fiber 配合。

01Render 树、Fiber 树、DOM 树:三棵「树」各司其职

很多人把三棵树混为一谈,其实它们分工完全不同:

Render 树:描述 UI 的轻量结构,只存类型/props/children,供 diff 算最小更新

Fiber 树:React 16+ 的工作单元,可中断、增量渲染、按优先级调度

DOM 树:浏览器真实渲染的节点,commit 阶段按需更新,操作代价最高

Fiber 节点的结构很关键:type、key、props、stateNode 记录组件信息,child、sibling、return 组成链表(替代了 React 15 的递归树),再通过 effect list 管理副作用,在 commit 阶段统一执行 DOM 更新和 hook 副作用。

图1 三棵树的关系:Render 描述 UI,Fiber 负责调度,DOM 才是真实页面

面试这样说

Render 树描述 UI,DOM 树才是真实页面;Fiber 支持可中断和增量渲染。哪怕大量组件同时更新,也不会每次全量渲染 DOM。

02一次 setState,到底走了哪几步

状态更新的源头有两类:Class 组件的 this.setState() ,以及 Function 组件的 useState setter / useReducer dispatch。触发后的完整链路是:

① 生成 Update 对象,加入该组件的更新队列

② Scheduler 读队列、判优先级,构建 work-in-progress Fiber 树

③ 新旧 Fiber 树 diff,计算最小 DOM 更新

④ Commit 阶段执行 DOM 更新 + 副作用(useEffect / useLayoutEffect)

图2 一次 setState 的完整旅程:入队 → 调度 → WIP 树 → diff → commit

关键认知:React 并非每次 setState 都重建整个 DOM,而是生成新的 WIP Fiber 树,通过 diff 只更新真正变化的节点。

面试这样说

要能讲清 state/props 改变如何进入队列、触发 reconcile,再落到 commit。这是渲染机制的主线。

03更新队列与批量更新:N 次 setState,只渲染一次

每个 Fiber 节点都维护自己的更新队列(update queue),存储待处理更新。好处是:多次 setState 累积合并,减少无效 render;再结合优先级调度,保证关键任务优先执行。

批量更新的版本差异是高频考点:

React 17 及之前:事件处理函数内的 setState 批量;Promise / setTimeout 不批量

React 18+:automatic batching,同步/异步更新都自动批量合并

const count, setCount = useState(0);

const handleClick = () => {

setCount(c => c + 1);

setCount(c => c + 1);

setCount(c => c + 1);

}; // React 18+ 最终只 render 一次,count 增加 3

注意这里必须用函数式更新 (c => c + 1):它会基于上一次的结果累加,而不是覆盖,所以三次 +1 最终变成 +3。

图3 同一优先级的多个 setSta

e 经 Scheduler 合并,最终只 render 一次

** 面试这样说**

批量更新机制结合 Fiber 的增量渲染共同优化性能;能举出「一个组件内部多次 setState 如何合并、避免重复 render」的例子才算过关。

04Scheduler:优先级调度,让页面不卡顿

React 16+ 引入调度器(Scheduler)管理任务优先级,避免主线程被长任务阻塞。优先级分三档:

同步任务:用户点击、输入,优先级最高

过渡任务(transition):页面切换、列表滚动,可中断

空闲任务(idle):非紧急更新,最低优先级

实现上,Fiber 把更新拆成小块(unit of work),用 MessageChannel / RAF 在浏览器空闲时间处理低优先级任务。经典场景:页面同时有表单输入和长列表加载,输入优先渲染,列表渲染可中断------页面因此不卡顿。

05高频场景优化:虚拟列表、批量更新与 RAF

长列表全部渲染会消耗大量内存、拖慢首屏。解法是虚拟列表(react-window / react-virtualized):只渲染可视区域节点,随滚动动态加载/卸载。配合 Fiber,滚动产生的节点更新被拆成小单元逐帧渲染,主线程不阻塞。

高频更新(滚动、拖拽)则用 requestAnimationFrame 与批量更新配合:RAF 在下一次绘制前触发回调,把多次状态更新合并进一个渲染周期。

let pending = false;

function updateState() {

if (!pending) {

复制代码
pending = true;

requestAnimationFrame(() => {

  setCount(c => c + 1);

  setCount(c => c + 1);

  pending = false;

});

}

};

这样避免高频更新触发大量 render,与 React 批量更新机制配合,减少 commit 次数。

06面试高频追问:快速对答

每次修改都会生成新的 render 树吗?

每次 setState 生成新的 work-in-progress Fiber 树,而非全量 DOM;最终 commit 只做最小化 DOM 更新。

渲染如何得知要更新?

状态或 props 改变会进入更新队列,由 Scheduler 调度生成新 Fiber 树,diff 计算更新。

一个组件里 99 个更新,什么时候批量?

每个 Fiber 有独立更新队列;React 合并同一优先级的更新,最终一次 render / commit 执行。

虚拟列表如何结合 Fiber 优化长列表?

只渲染可视区域节点,滚动触发增量 Fiber 更新,分帧渲染减少主线程压力。

高频更新怎么优化?

结合 RAF + React 自动批处理,合并多次状态更新,减少 render 次数,保证 UI 流畅。

落地总结

把这条主线记牢:setState → 更新队列 → Scheduler 调度 → WIP Fiber 树 → diff → commit。Render 树描述 UI,Fiber 管调度,DOM 才是真实页面。

无论有多少子组件同时 setState,React 都会通过批量更新 + Fiber 增量渲染,只触发必要的 render------这就是它高性能的底层答案。

关注我们,一起把技术讲明白

**寻码札记 **

相关推荐
恋猫de小郭1 小时前
Meta 分享怎么用 AI 迁移 Compose 项目不烧心
android·前端·flutter
Ai-_Man1 小时前
请问豆包的智能体聊天记录该怎么弄
开发语言·前端·javascript·人工智能·小程序·ecmascript·电脑
IT_陈寒1 小时前
SpringBoot自动配置把我坑惨了:这些隐式规则要小心
前端·人工智能·后端
এ慕ོ冬℘゜2 小时前
es6基础知识
前端·javascript·es6
吠品2 小时前
DicomViewer24 的 CI 又红了:MPR 测试偶发失败排查记
java·服务器·前端
泡海椒5 小时前
JQuick-Excel FORMAT 导出格式实战:日期、金额显示与 TRANSFORM 的边界
前端·python·excel
迅猛龙办公室11 小时前
Python输出当前计算机的系统日期和时间
开发语言·前端·python
小狼1545411 小时前
浏览器扩展脚本为什么有时候不生效:注入时机、iframe 和单页路由,多多开票助手
前端·chrome
じòぴé南冸じょうげん11 小时前
油猴脚本突然发现变成灰色了,无法使用?页面不生效?刷新页面没反应?
前端