当前位置: 首页 > news >正文

React 核心原理与Fiber架构

目录

一、虚拟 DOM

二、Diffing 算法

三、Fiber 架构

四、渲染流程

1. Render 阶段(可中断异步过程)

2. Commit 阶段(同步不可中断)

五、时间切片(Time Slicing)

六、核心流程步骤总结

1. 状态更新触发

2. Render 阶段(异步可中断,构建 Fiber 树)

3. Commit 阶段(同步不可中断,更新真实 DOM)

4. 双缓存机制切换

5. 调度系统核心支撑

七、组件触发渲染的时机


相关内容: 

React Fiber 架构原理:关于 Fiber 树的一切

React核心原理浅析

二十分钟掌握React核心理念,老鸟快速入门指南


一、虚拟 DOM

React 使用虚拟 DOM 来表示 UI 的状态。虚拟 DOM 是一个轻量级的 JavaScript 对象,每个节点包含 tag(标签名)、props(属性)、children(子节点)。

核心价值在于性能优化 —— 直接操作真实 DOM 会触发浏览器重排 / 重绘,成本很高;而虚拟 DOM 先在内存中通过 Diff 算法比对状态变更,再批量更新真实 DOM,减少浏览器操作次数。

二、Diffing 算法

Diffing 算法用于比较新旧虚拟 DOM 树,以最小的操作次数将旧 DOM 树转换为新 DOM 树。

React 提出了复杂度为 O(n) 的启发式算法,通过设置 key 属性来标识一组同级子元素,从而高效地更新真实 DOM。

  1. 同层比较(Tree Diff):仅逐层对比节点,不跨层级遍历。若节点类型不同,直接销毁整棵子树及其组件实例并重建。
  2. Key 值优化:列表渲染时需为元素提供唯一 key,用于标识节点身份。通过 key,React 可识别节点的移动或复用,避免全量更新。
  3. 组件类型比对:若组件类型相同,则递归更新子节点;若类型不同,则卸载旧组件并挂载新组件。

不指定 key 的后果:

React 通过 ​​key + 组件类型​​ 的组合来识别元素的身份,没有 key 时默认使用 ​​数组索引​​ 作为 key。

若用索引作为 key,当列表项顺序变化或中间插入 / 删除元素时,React 会错误地认为大量节点需重新创建,而非移动或更新,导致不必要的 DOM 操作(如重复卸载 / 挂载组件),严重影响性能。

索引作为 key 的问题本质:

  • ​绑定问题​​:使用索引作为 key 时状态绑定到​位置​,而非​数据​​
  • DOM复用规则​​:React 只复用相同 key 对应的 DOM 节点
  • ​数据与DOM分离​​:React 更新内容但不更新状态
  • ​状态漂移​​:输入状态留在原位置,被新元素继承

使用唯一 ID 作为 key 可以解决这个问题,因为它确保状态与数据项(而非位置)保持一致关联。


三、Fiber 架构

Fiber 架构解决了传统同步渲染阻塞主线程的问题,实现可中断的异步渲染,支持时间切片和优先级调度。

Fiber 节点结构

每个组件对应一个 Fiber 节点,构成链表树(非传统递归树)。节点包含组件类型、状态、副作用标记(effectTag,如删除、新增节点)、节点指针:child(指向第一个子节点)、sibling(指向下一个兄弟节点)、return(指向父节点)。

双缓存机制

  • Current Tree:当前已渲染到页面的 Fiber 树。
  • WorkInProgress Tree:后台构建的新 Fiber 树,用于计算变更。

两棵树通过 alternate 指针关联,每次更新时新建 WorkInProgress Tree,构建完成后直接替换Current Tree,保证视图连续性。


四、渲染流程

1. Render 阶段(可中断异步过程)

构建 Fiber 链表树,通过 Diff 标记副作用(如节点增删)。

1. 深度优先遍历
从根节点开始,采用深度优先遍历,通过 beginWork 向下处理每个 Fiber 节点,逐步构建 Fiber 链表树。

2. Diffing 算法执行
对比新旧子节点,决定复用/移动/删除,并标记 effectTag(如 Placement 移动节点)。

  • 节点复用条件:父节点已复用,且 key 和 type 相同。
  • 子节点 Diff 顺序:先尝试单节点匹配,再处理多节点末尾增删(一轮循环),最后处理复杂移动场景(二轮循环),尽可能减少节点移动开销。

3. 向上回溯
当节点无子节点时,进入 completeUnitOfWork,自底向上收集 effectTag,将子节点的 effectList 合并到父节点,最终形成从根节点到叶节点的副作用链表。

4. 中断与恢复
利用时间切片(Time Slicing)将任务拆分为微任务,在浏览器空闲时执行,避免阻塞主线程。每处理完一个节点,检查剩余时间片,时间耗尽时暂停,通过全局变量保存进度,浏览器空闲时通过调度器恢复任务。

2. Commit 阶段(同步不可中断)

批量执行副作用,更新真实 DOM。此阶段必须一气呵成,确保 DOM 操作的原子性。

遍历 effectList,批量更新真实 DOM(执行 effectTag 对应的操作,如创建、删除节点)。触发回调,处理 ref 和 useEffect。


五、时间切片(Time Slicing)

时间切片策略将渲染任务拆分为微任务单元,利用浏览器空闲时段执行,提升响应性。

调度器:模拟 requestIdleCallback 功能(兼容旧浏览器),设置任务优先级,通过 MessageChannel 实现异步调度,将任务拆分为小单元,每次执行完一个单元后,检查是否有高优先级任务插队,若有则暂停当前任务。

任务优先级:分为五级(紧急交互 > 过渡动画 > 普通更新 > 延迟更新 > 过期任务),高优先级任务可插队执行,中断低优先级任务;低优先级任务可暂停或丢弃,避免占用主线程。例如,用户点击按钮时,渲染任务会被暂停,优先处理点击回调。

调度系统通过四层架构实现:

  1. SchedulerHostConfig:对接浏览器底层能力,利用 MessageChannel 计算空闲时间,提供空闲回调机制,是 Fiber 调度的基础。
  2. Scheduler:核心任务管理模块,定义五级优先级,通过双向循环链表维护任务池,实现任务的注册、取消和优先级排序,并在浏览器空闲时执行任务。
  3. SchedulerWithReactIntegration:抹平调度接口,将 Scheduler 与 React 的更新流程整合,例如在状态更新时触发调度。
  4. ReactFiberScheduler:应用层调度入口,将 React 的更新任务(如 Fiber 树的构建)包装为 Scheduler 可处理的任务,在 Render 阶段通过 shouldYield() 检查是否需要中断任务,确保主线程不阻塞。

六、核心流程步骤总结

1. 状态更新触发

  • 因用户交互(如点击)、setState 或 Hooks 更新函数调用,触发组件状态变更,生成新虚拟 DOM,启动更新流程。

2. Render 阶段(异步可中断,构建 Fiber 树)

  • 任务拆分与优先级调度:利用 时间切片 将渲染任务拆分为微任务,通过调度器按优先级异步执行,可被高优先级任务中断。
  • Fiber 树构建与 Diff 执行
    • 深度优先遍历,通过 beginWork 对比新旧虚拟 DOM,复用 key 和类型相同的节点,标记新增 / 更新 / 删除的 副作用(effectTag)
    • 子节点 Diff 按 “单节点匹配 → 末尾增删 → 复杂移动” 顺序优化,减少 DOM 操作。
  • 副作用收集:通过 completeUnitOfWork 自底向上合并副作用,形成根节点的 effectList 链表
  • 中断与恢复:每处理完一个 Fiber 节点,检查时间片是否耗尽,耗尽时暂停任务并保存进度,浏览器空闲时恢复。

3. Commit 阶段(同步不可中断,更新真实 DOM)

  • 遍历 effectList,批量执行 DOM 操作(创建、删除、更新节点),确保操作原子性。
  • 触发生命周期回调(如 useEffectref 更新),完成视图渲染。

4. 双缓存机制切换

  • 构建完成的 WorkInProgress Tree 替换为 Current Tree,通过 alternate 指针复用节点数据,保证视图连续性。

5. 调度系统核心支撑

  • 通过 四层架构(SchedulerHostConfig、Scheduler、SchedulerWithReactIntegration、ReactFiberScheduler)实现任务优先级管理、时间切片和异步调度,避免主线程阻塞。

七、组件触发渲染的时机

  • 状态(state)更新:调用更新函数导致组件状态变化。
  • props 变化:父组件传递的 props 值或引用发生改变。
  • 上下文(Context)变化:组件依赖的 Context 值更新。
  • 父组件渲染:父组件重新渲染触发子组件默认更新(未优化时)。
  • 强制更新:调用forceUpdate()跳过常规更新判断。
  • 组件 key 变化:触发旧组件卸载和新组件挂载(相当于重新渲染)。

http://www.lryc.cn/news/573673.html

相关文章:

  • java中关于异步转同步的一些解决方案的对比与思考。【spring mvc堵塞式】
  • 【前后前】导入Excel文件闭环模型:Vue3前端上传Excel文件,【Java后端接收、解析、返回数据】,Vue3前端接收展示数据
  • 华为云Flexus+DeepSeek征文|在Dify-LLM平台中开发童话故事精灵工作流AI Agent
  • 【DDD】——带你领略领域驱动设计的独特魅力
  • C4.5算法深度解析:决策树进化的里程碑
  • 《HTTP权威指南》 第7章 缓存
  • mysql join的原理及过程
  • C++法则10:引用本身是一个“别名”(alias),一旦绑定到一个对象后,就不能再重新绑定到其他对象。
  • 【递归,搜索与回溯算法】记忆化搜索(二)
  • 如何处理RocketMQ的各种线上问题
  • 【Python学习笔记】报错:Unindent amount does not match previous indent
  • Spring Boot 项目初始化
  • AWS 使用图形化界面创建 EKS 集群(零基础教程)
  • LabVIEW图像拼接原理与实现 链接附件有演示录像
  • 如何用AI开发完整的小程序<9>—UI自适应与游戏页优化
  • 关于uniapp解析SSE响应数据的处理
  • 【学习笔记】深入理解Java虚拟机学习笔记——第11章 后端编译与优化
  • 关于CH32开发板烧录说明
  • 用可观测工具高效定位和查找设计中深度隐藏的bug
  • webpack+vite前端构建工具 -6从loader本质看各种语言处理 7webpack处理html
  • Linux内核中安全创建套接字:为何inet_create未导出及正确替代方案
  • SAP金属行业解决方案:无锡哲讯科技助力企业数字化转型与高效运营
  • Kafka Streams架构深度解析:从并行处理到容错机制的全链路实践
  • 针对数据仓库方向的大数据算法工程师面试经验总结
  • netcore url编码/解码
  • [计算机网络] 局域网内的网络传输
  • SpringBoot+Vue服装商城系统 附带详细运行指导视频
  • 3dgs涉及的基本概念:球谐系数(SH 系数)等
  • Python之数据容器
  • 【JavaScript】代码整理