react - Fiber架构初步理解
文章目录一、引言useState 的状态丢失谜题1.1 闭包Closure的作用与陷阱⚠️ 闭包陷阱与批处理机制1.2 Fiber 节点的状态存储机制二、React Fiber 架构深度剖析2.1 Fiber 节点的核心数据结构2.2 Fiber 节点的组织形式真正的链表树2.3 Hooks 在 Fiber 中的单向链表排布2.4 为什么 React 选用链表而不是数组2.5 双缓存机制Double Buffering2.6 Fiber 树的 DFS 遍历过程与伪代码三、链表数据结构基础与 JS 实现3.1 什么是单链表3.2 单链表的 JavaScript 实现3.3 链表 vs 数组 性能对比四、总结一、引言useState 的状态丢失谜题在 React 函数组件中每次组件重新渲染组件函数都会从头到尾重新执行一次。但为什么通过useState定义的状态State却能在多次渲染之间持久保存更新后的值而不会被重复初始化呢其背后的底层机制涉及闭包Closure与React Fiber 架构的紧密协作。1.1 闭包Closure的作用与陷阱useState返回的状态值如count和更新函数如setCount通过闭包捕获了当前渲染周期的状态快照。function Counter() { const [count, setCount] useState(0); // 闭包捕获当前渲染周期的 count 值 const handleClick () { // 提交更新闭包中的 count 是当前渲染周期的值例如 0 setCount(count 1); }; return button onClick{handleClick}{count}/button; }⚠️ 闭包陷阱与批处理机制看下面这个常见场景function Counter() { const [count, setCount] useState(0); const handleClick () { setCount(count 1); // 第一次更新基于当前闭包 count 0 setCount(count 1); // 第二次更新仍基于当前闭包 count 0 }; // 点击后 count 只会变为 1而不是 2 return button onClick{handleClick}{count}/button; }原因一闭包在同一次事件回调中两次count拿到的都是旧快照0。原因二批处理与计算传入的是具体数值setCount(1)React 在下一轮合并更新时会将旧值覆盖。解决方案如果需要依赖最新状态需改用函数式更新setCount(prev prev 1)React 会将更新函数放入队列依次计算出最终状态。1.2 Fiber 节点的状态存储机制闭包负责在单次渲染中提供状态快照而真正跨渲染周期存储状态的地方是 React 的 Fiber 节点。每个函数组件在 React 内部都对应一个 Fiber 节点其状态链表存储在 Fiber 节点的memoizedState属性中。组件重新渲染时useState不会重新初始化状态而是直接从 Fiber 节点的memoizedState中读取最新计算好的状态值。// Fiber 节点存储 Hook 状态的结构示意{memoizedState:{memoizedState:42,// 当前 Hook 的状态值queue:{...},// 待处理的 setState 更新队列next:{...}// 指向下一个 Hook}}二、React Fiber 架构深度剖析在 React 16 之前Stack Reconciler渲染过程通过原生的递归函数调用栈完成一旦开始就无法中断容易导致主线程卡顿。引入Fiber 架构后React 将树形结构改造成了指针链表从而实现了可中断、可恢复的增量渲染。2.1 Fiber 节点的核心数据结构每一个 Fiber 节点都是一个 JavaScript 对象包含了组件类型、状态、DOM 节点以及调度所需的链表指针interfaceFiber{// 1. 标识组件/节点类型tag:WorkTag;// 如 FunctionComponent, ClassComponent, HostComponenttype:any;// 对于函数组件type 是组件函数本身对于 DOM 节点是 div 等字符串// 2. 状态与副作用存储memoizedState:any;// 状态存储函数组件的 Hooks 单向链表保存在这里stateNode:any;// 对应的真实 DOM 节点或类组件实例updateQueue:UpdateQueueany|null;// 状态更新队列flags:Flags;// 副作用标记如 Placement 插入、Update 更新、Deletion 删除// 3. 核心指针链表构成 Fiber 树的关键child:Fiber|null;// 指向“第一个子节点”sibling:Fiber|null;// 指向“下一个兄弟节点”return:Fiber|null;// 指向“父节点”// 4. 双缓存机制指针alternate:Fiber|null;// 指向另一棵树current - workInProgress对应的 Fiber 节点}2.2 Fiber 节点的组织形式真正的链表树以如下组件结构为例function App() { return ( div Header / Content / /div ); }传统树形结构是用数组包含子节点而Fiber 树本质上是一棵通过指针串联起来的单链表架构App Fiber | (child) div Fiber | (child) Header Fiber ----(sibling)---- Content Fiber | (return) | (return) ----------------------------------- div Fiber重点父节点的child仅仅指向第一个子节点Header。子节点通过sibling链表指向下一个兄弟节点Content。所有子节点通过return指向其父节点div。2.3 Hooks 在 Fiber 中的单向链表排布函数组件内的多个 Hook如useState,useEffect按调用顺序存在于 Fiber 的memoizedState中以单向链表的形式组织function Example() { const [count, setCount] useState(0); // Hook 1 const [name, setName] useState(); // Hook 2 useEffect(() {}, []); // Hook 3 }对应的 Hook 内存链表Fiber.memoizedState | Hook1 (count) ----next---- Hook2 (name) ----next---- Hook3 (effect) ---- null | | | memoizedState: 0 memoizedState: memoizedState: { ... }为什么 React 规定 Hook 不能写在条件语句或循环中因为 React 内部依靠单向链表的游标指针按 Hook 执行顺序逐个读取节点。如果条件分支导致某个 Hook 被跳过链表结点的匹配就会全部错位2.4 为什么 React 选用链表而不是数组可中断与恢复最核心原因原生的递归调用栈无法暂停。而使用链表指针模拟调用栈后React 可以将当前工作节点记录在一个全局指针如workInProgress中。当浏览器帧率不足通过Scheduler调度时React 随时可以中断Pause渲染让出主线程下一帧再从记录的指针处恢复Resume遍历。高效的结构变更在树的 DOM 节点插入、删除、移动时链表只需要修改指针如child或sibling操作的时间复杂度为O ( 1 ) O(1)O(1)效率极高。保证 Hook 调用的顺序确定性单向链表天生具备严格的前后依赖关系能以极低的开销保障 Hook 调用的固定顺序。2.5 双缓存机制Double Buffering在 2.1 节提到了alternate属性这是 React 优化 DOM 渲染的核心思想——双缓存机制current树代表当前在屏幕上显示的真实 DOM 对应的 Fiber 树。workInProgress树代表正在内存中构建/更新的 Fiber 树。当触发更新时React 会在后台基于current树构建全新的workInProgress树。所有 Diff 比较与副作用标记都在后台完成后React 只需要将指针切换一次直接把workInProgress替换为current即可无缝完成界面更新避免中间状态导致的页面闪烁。2.6 Fiber 树的 DFS 遍历过程与伪代码React 遍历 Fiber 树采用的是深度优先遍历DFS整个过程被称为WorkLoop工作循环// 模拟 Fiber 树单次单元工作的伪代码functionperformUnitOfWork(fiber){// 1. 执行当前 Fiber 节点的 reconcile/更新逻辑 ...// 2. 如果有子节点优先深入向下遍历 childif(fiber.child){returnfiber.child;}// 3. 如果没有子节点说明到了叶子节点查找兄弟节点或向父节点回溯letnextFiberfiber;while(nextFiber){// 完成当前节点的“归complete Work”逻辑 ...// 如果有兄弟节点走 sibling 指针if(nextFiber.sibling){returnnextFiber.sibling;}// 否则回溯给父节点 return 指针继续向上查找nextFibernextFiber.return;}returnnull;// 回溯到 Root 节点遍历结束}三、链表数据结构基础与 JS 实现为了更好地理解 Fiber 的设计我们回顾一下数据结构中的单链表原理。3.1 什么是单链表链表Linked List是一种线性数据结构由一系列节点Node组成。节点在内存中无需连续存储每个节点包含数据域存储节点的值/状态指针域指向下一个节点的引用3.2 单链表的 JavaScript 实现// 1. 定义链表节点结构classListNodeT{value:T;next:ListNodeT|null;constructor(value:T){this.valuevalue;this.nextnull;}}// 2. 创建单链表: 1 - 2 - 3constnode1newListNode(1);constnode2newListNode(2);constnode3newListNode(3);node1.nextnode2;node2.nextnode3;// 3. 遍历单链表letcurrent:ListNodenumber|nullnode1;while(current!null){console.log(current.value);// 依次输出: 1, 2, 3currentcurrent.next;}3.3 链表 vs 数组 性能对比特性数组 (Array)链表 (Linked List)内存分配连续内存空间动态随机分配内存非连续随机访问 (Index)快时间复杂度O ( 1 ) O(1)O(1)慢需要沿指针遍历时间复杂度O ( n ) O(n)O(n)头部/中间插入与删除慢需要移动后续所有元素O ( n ) O(n)O(n)快只需改变对应指针指向O ( 1 ) O(1)O(1)适用场景需要快速检索/遍历元素的场景元素频繁变动、大小不固定、需要随时中断/中断恢复的场景四、总结React 将复杂的 UI 界面更新拆解为高效率的计算调度其核心贯穿逻辑如下useState的存储依赖组件对应的Fiber 节点其内部以单向链表的形式在memoizedState中保存每个 Hook 的状态。闭包与快照组件函数每次渲染通过闭包获取当次渲染的状态快照修改状态时使用函数式更新可以避免闭包拿旧值的问题。Fiber 的链表拓扑Fiber 节点通过child子、sibling兄、return父三条指针将组件树串联为链表结构。可中断渲染的基石正是基于这种显示的指针链表结构React 才能脱离原生 JS 递归栈的限制实现无卡顿的增量渲染Incremental Rendering与双缓存机制。 感谢阅读想了解更多 我的博客网站 | 记录思考分享干货 我的个人主页 | 关于我、开源项目

相关新闻

多场景智能外呼怎么选?外呼机器人落地全科普指南

多场景智能外呼怎么选?外呼机器人落地全科普指南

不少企业管理者在数字化拓客、客户服务过程中,始终被四大难题困扰:人工外呼人力成本居高不下,批量触达效率极低;市面上外呼机器人对话机械生硬,客户接通直接挂断;系统无法对接企业现有客户管理工具&#xf…

2026/10/1 12:53:45 阅读更多 →
Linux systemd 服务管理实战:Unit 文件、开机自启

Linux systemd 服务管理实战:Unit 文件、开机自启

现代 Linux 都用 systemd 管理服务,替代了老的 SysVinit。部署 Java/Python 项目、写开机自启、看服务日志,全都要用 systemd。这篇从 Unit 文件写到实战部署。 一、什么是 systemd systemd 系统和服务管理器老方式 service(SysVinit&#x…

2026/9/30 11:47:03 阅读更多 →
Markdown本地图片预览全攻略:原理、方案与最佳实践

Markdown本地图片预览全攻略:原理、方案与最佳实践

1. 项目概述:为什么我们需要在.md文件中优雅地插入本地图片?如果你经常用Markdown写文档、记笔记,或者维护项目README,肯定遇到过这个烦心事:辛辛苦苦在.md文件里插入了本地图片的路径,结果在编辑器里只看到…

2026/10/1 7:03:20 阅读更多 →

最新新闻

CPS是什么?信息物理系统与客户编程软件汉化补丁解析

CPS是什么?信息物理系统与客户编程软件汉化补丁解析

CPS 这三个字母,我在过去一年里至少在两个完全不同的技术社群里反复撞见过。做工业数字化的朋友张口就是信息物理系统、数字孪生、边缘控制器;玩对讲机的老哥嘴里念叨的却是"GP88S 的 CPS r02.02 汉化补丁到底怎么装"。同一个缩写,…

2026/10/1 12:52:59 阅读更多 →
PROFINET掉站闪断响应慢怎么办?现场排查思路与实战指南

PROFINET掉站闪断响应慢怎么办?现场排查思路与实战指南

搞工控的兄弟,谁没被 PROFINET 折腾过几回?设备在实验室里跑得好好的,一拉到现场就动不动掉站、闪断,要么就是响应慢半拍,找半天找不出原因。尤其是碰到机器人、伺服这种对实时性要求高的设备,系统闹起脾气…

2026/10/1 12:52:59 阅读更多 →
游戏闪退+缺少DLL?别急着下载,按这4步排查修复

游戏闪退+缺少DLL?别急着下载,按这4步排查修复

你刚排进无畏契约的选人阶段,屏幕一卡,还没等反应过来就回到了桌面;或者在三角洲行动的加载界面,连"开始行动"都没看到,游戏直接消失。这种闪退还带一个报错弹窗,上面写着"缺少 dll 文件&qu…

2026/10/1 12:52:59 阅读更多 →
Qwen2-72B+ vLLM 实现200k上下文稳定推理实战

Qwen2-72B+ vLLM 实现200k上下文稳定推理实战

1. 项目概述:不是“平替”,而是重新定义长上下文能力的实战方案最近刷到不少标题党说“XX模型是ChatGPT-4.0最强平替”,点进去一看,要么是拿32k上下文的开源模型硬凑热度,要么直接把API调用封装成网页就敢标榜“200k”…

2026/10/1 12:52:59 阅读更多 →
Redis 接入 AI 实战:向量检索、语义缓存与 Agent 记忆层

Redis 接入 AI 实战:向量检索、语义缓存与 Agent 记忆层

1. 从“Redis 接入 AI”说起:这件事到底意味着什么Redis 这个名字,做后端开发的人基本都绕不开。缓存、分布式锁、消息队列、排行榜、会话存储,几乎每个稍微有点规模的项目里都能看到它的身影。但“Redis 已正式接入 AI”这个说法&#xff0c…

2026/10/1 12:52:59 阅读更多 →
Strands Agents Harness SDK:从原型到生产级Agent开发实战

Strands Agents Harness SDK:从原型到生产级Agent开发实战

Agent 开发这件事,很多人第一次接触时都会有一种"我是不是把它想复杂了"的错觉。你打开一个主流框架的文档,跟着写一个 ReAct 循环,跑通了,感觉挺好;然后你想加个工具调用、加个多轮记忆、加个流式输出、加个…

2026/10/1 12:51:58 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →