news 2026/10/8 8:03:15

just-react 源码解析:React16 Fiber 架构的起源、含义与数据结构详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
just-react 源码解析:React16 Fiber 架构的起源、含义与数据结构详解
  • 文档
  • 前端

【免费下载链接】just-react

「React技术揭秘」 一本自顶向下的React源码分析书

项目地址:https://gitcode.com/gh_mirrors/ju/just-react
点击查看免费下载

导读

本文基于《React 技术揭秘》(just-react)仓库的 docs/process/fiber.md 展开,系统讲解Fiber作为 React16 全新架构核心的来龙去脉:它因何取代 React15 的虚拟 DOM 与递归 Reconciler,作为「架构 / 静态数据结构 / 动态工作单元」的三层含义,以及FiberNode构造函数的全部核心字段。读完本文,你将掌握Fiber 树的连接方式(return / child / sibling)、静态与动态属性各自的职责边界,以及lanes优先级模型取代expirationTime的演进背景,为后续理解 render 阶段(beginWork、completeWork)与双缓存机制打下坚实基础。


一、Fiber 的起源:从「递归不可中断」到「异步可中断」

在新的 React 架构一节中提到的虚拟 DOM,在 React 中有个正式的称呼——Fiber。在后续学习中,我们会逐渐用Fiber来取代「React16 虚拟 DOM」这一称呼。

要理解Fiber因何而来,需要先回顾 React15 的架构局限:

  • 在React15及以前,Reconciler(协调器)采用递归的方式创建虚拟 DOM,递归过程是不能中断的。
  • 如果组件树的层级很深,递归会占用线程很多时间,造成卡顿。

React 核心团队成员 Acdlite 在 2016 年发布的 react-fiber-architecture 是Fiber最早也是最具权威性的官方解释来源,本文对 Fiber 的描述与之保持一致。

为了解决「递归无法中断」的问题,React16将递归的无法中断的更新重构为异步的可中断更新。由于曾经用于递归的虚拟 DOM数据结构已经无法满足「可中断、可恢复」的需求,全新的Fiber架构应运而生。

补充:这种「异步可中断更新」本质上是在践行函数式编程中的代数效应(Algebraic Effects)思想。仓库的 fiber-mental.md 详细解释了为什么React没有采用Generator实现协调器:Generator是「传染性」的,且其中间状态是上下文关联的,当高优先级任务插队时无法复用已计算的中间结果。React Fiber则是React内部实现的一套支持任务不同优先级、可中断与恢复、且恢复后可以复用之前中间状态的状态更新机制。


二、Fiber 的三层含义

Fiber并非一个单一概念,它同时承载三层含义:

  1. 作为架构来说:之前React15的Reconciler采用递归的方式执行,数据保存在递归调用栈中,所以被称为stack Reconciler。React16的Reconciler基于Fiber节点实现,被称为Fiber Reconciler。

  2. 作为静态的数据结构来说:每个Fiber节点对应一个React element,保存了该组件的类型(函数组件 / 类组件 / 原生组件……)、对应的 DOM 节点等信息。

  3. 作为动态的工作单元来说:每个Fiber节点保存了本次更新中该组件改变的状态、要执行的工作(需要被删除 / 被插入页面中 / 被更新……)。

这层「架构 / 静态数据结构 / 动态工作单元」的分类,将直接作为下文解析FiberNode各字段的线索。


三、Fiber 的结构:FiberNode 构造函数全解

FiberNode的属性很多,但可以按上述三层含义分类来看。核心定义(对应 React 源码packages/react-reconciler/src/ReactFiber.new.js中的FiberNode构造函数)如下:

function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) { // 作为静态数据结构的属性 this.tag = tag; this.key = key; this.elementType = null; this.type = null; this.stateNode = null; // 用于连接其他Fiber节点形成Fiber树 this.return = null; this.child = null; this.sibling = null; this.index = 0; this.ref = null; // 作为动态的工作单元的属性 this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.dependencies = null; this.mode = mode; this.effectTag = NoEffect; this.nextEffect = null; this.firstEffect = null; this.lastEffect = null; // 调度优先级相关 this.lanes = NoLanes; this.childLanes = NoLanes; // 指向该fiber在另一次更新时对应的fiber this.alternate = null; }

注:仓库本身不包含 React 源码,上述代码为《React 技术揭秘》依据 React 官方源码整理的结构,字段含义与实际源码一一对应。当前本书对应版本为v17.0.0-alpha(见 docs/index.md),如需调试最新源码,可参照 docs/preparation/source.md 中「拉取 master 分支 → 构建 react/scheduler/react-dom 三个包 → create-react-app 引用本地包」的流程。

下面按三层含义逐一展开。


四、作为架构:Fiber 树的连接方式

每个Fiber节点都有一个对应的React element,多个Fiber节点通过如下三个属性连接成树:

// 指向父级Fiber节点 this.return = null; // 指向子Fiber节点 this.child = null; // 指向右边第一个兄弟Fiber节点 this.sibling = null;

为什么父级指针叫return而不是parent?

这是一个非常关键的设计细节:因为Fiber是工作单元,return指的是节点执行完completeWork后会返回的下一个节点。子Fiber节点及其兄弟节点完成工作后会返回其父级节点,所以用return指代父级节点。这一命名体现了「Fiber 以工作流而非静态树为视角」的架构设计思想,也与 docs/process/reconciler.md 中 render 阶段「递 / 归」两阶段的遍历方式直接对应。

举例:组件结构与 Fiber 树

考虑如下组件结构:

function App() { return ( <div> i am <span>KaSong</span> </div> ) }

其对应的Fiber树结构为:

  • App Fiber(父)
    • div Fiber(child)
      • "i am" Fiber(child)
      • span Fiber(sibling,即"i am" Fiber的右边第一个兄弟)

关于这棵 Fiber 树如何被深度优先遍历构建,可参见 docs/process/reconciler.md:render 阶段从rootFiber开始向下深度优先遍历,为每个节点调用beginWork(「递」)与completeWork(「归」),两阶段交错执行直到归回rootFiber。


五、作为静态的数据结构:组件信息载体

作为一种静态的数据结构,Fiber保存了组件相关的信息:

// Fiber对应组件的类型 Function/Class/Host... this.tag = tag; // key属性 this.key = key; // 大部分情况同type,某些情况不同,比如FunctionComponent使用React.memo包裹 this.elementType = null; // 对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagName this.type = null; // Fiber对应的真实DOM节点 this.stateNode = null;

各字段职责如下:

字段含义
tagFiber对应组件的类型,如FunctionComponent、ClassComponent、HostComponent等
key组件的key属性,用于 Diff 时识别节点
elementType大部分情况与type相同;某些情况不同,例如FunctionComponent使用React.memo包裹时
type对FunctionComponent指函数本身;对ClassComponent指 class;对HostComponent指 DOM 节点的tagName
stateNodeFiber对应的真实 DOM 节点

佐证:在 docs/process/beginWork.md 中,beginWork正是根据workInProgress.tag的不同(IndeterminateComponent、LazyComponent、FunctionComponent、ClassComponent、HostRoot、HostComponent、HostText等)进入不同类型Fiber的创建逻辑,最终统一进入reconcileChildren。


六、作为动态的工作单元:本次更新相关信息

作为动态的工作单元,Fiber中如下参数保存了本次更新相关的信息:

// 保存本次更新造成的状态改变相关信息 this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.dependencies = null; this.mode = mode; // 保存本次更新会造成的DOM操作 this.effectTag = NoEffect; this.nextEffect = null; this.firstEffect = null; this.lastEffect = null;

这些属性会在后续的更新流程中用到时再详细介绍,这里先建立整体认知:

  • pendingProps/memoizedProps:分别保存「本次更新传入的新 props」与「上一次更新完成的 props」。在 docs/process/beginWork.md 的 update 优化路径中,正是通过oldProps !== newProps(即current.memoizedProps与workInProgress.pendingProps的比较)判断是否可以直接复用current节点。
  • updateQueue:更新队列。在 docs/process/completeWork.md 中,updateHostComponent处理完的 props 会被赋值给workInProgress.updateQueue(updatePayload为数组,偶数索引是变化的 prop key,奇数索引是变化的 prop value),最终在 commit 阶段渲染到页面。
  • memoizedState:上一次更新完成的 state,Hooks 相关章节会大量使用。
  • effectTag/nextEffect/firstEffect/lastEffect:保存本次更新会造成的 DOM 操作。effectTag使用二进制位表示,例如:
// DOM需要插入到页面中 export const Placement = /* */ 0b00000000000010; // DOM需要更新 export const Update = /* */ 0b00000000000100; // DOM需要插入到页面中并更新 export const PlacementAndUpdate = /* */ 0b00000000000110; // DOM需要删除 export const Deletion = /* */ 0b00000000001000;

通过二进制表示effectTag,可以方便地使用位操作一次为fiber.effectTag赋值多个 effect(如Placement | Update)。

佐证:firstEffect/lastEffect与 docs/process/completeWork.md 中effectList机制直接相关——每个执行完completeWork且存在effectTag的Fiber节点会被保存在一条名为effectList的单向链表中,第一个节点保存在fiber.firstEffect,最后一个保存在fiber.lastEffect。这样 commit 阶段无需重新遍历整棵 Fiber 树,只需遍历effectList即可执行所有 effect。

调度优先级相关字段

如下两个字段保存调度优先级相关的信息,会在讲解Scheduler时详细介绍:

// 调度优先级相关 this.lanes = NoLanes; this.childLanes = NoLanes;

⚠️ 注意:优先级模型的历史演进

在2020 年 5 月,调度优先级策略经历了比较大的重构:以expirationTime属性为代表的优先级模型被lane取代(对应 React 官方 PR #18796)。因此:

  • 如果你的源码中fiber.expirationTime仍存在,说明该版本早于这次重构;
  • 请参照 调试源码章节 获取最新代码(本书基于v17.0.0-alpha及以后版本讲解)。

仓库 docs/concurrent/lane.md 对lane模型有专门讲解,docs/concurrent/scheduler.md则讲解Scheduler的原理与实现,可与本节衔接阅读。

alternate 属性:双缓存机制的基石

// 指向该fiber在另一次更新时对应的fiber this.alternate = null;

alternate虽然未在原文档的三层分类中单列,但它是理解 Fiber 动态工作单元的关键补充:current Fiber树中的current fiber与workInProgress Fiber树中的workInProgress fiber通过alternate属性连接:

currentFiber.alternate === workInProgressFiber; workInProgressFiber.alternate === currentFiber;

这也是 docs/process/doubleBuffer.md 所讲的「双缓存」机制的核心:React 在内存中构建workInProgress Fiber树,构建完成后通过切换应用根节点fiberRootNode的current指针完成树的替换,对应 DOM 的创建与更新。


七、总结与下一步

本节我们了解了Fiber的起源与架构:

  • 起源:React15 递归式stack Reconciler无法中断,React16 将其重构为异步可中断的Fiber Reconciler;
  • 三层含义:作为架构(Fiber Reconciler)、作为静态数据结构(保存组件类型、DOM 节点等信息)、作为动态工作单元(保存本次更新的状态改变与待执行工作);
  • 结构:FiberNode通过return / child / sibling三个属性连接成Fiber树,通过tag / key / type / stateNode等保存组件信息,通过effectTag等记录 DOM 操作,通过lanes / childLanes表达调度优先级,通过alternate衔接双缓存的两棵树。

Fiber节点可以构成Fiber树。那么Fiber树和页面呈现的DOM树有什么关系,React 又是如何更新 DOM 的呢?这正是下一篇 docs/process/doubleBuffer.md(双缓存机制)要解决的问题;而 Fiber 树在 render 阶段的具体构建过程,则由 docs/process/reconciler.md、docs/process/beginWork.md、docs/process/completeWork.md 依次展开。


参考资料

  • 《React 技术揭秘》Fiber 架构的实现原理(本文主体)
  • Fiber 架构的心智模型:代数效应、Generator 与 Fiber 的关系
  • 新的 React 架构:Scheduler / Reconciler / Renderer 三层架构
  • render 阶段流程概览:「递 / 归」两阶段与 workLoop
  • beginWork:递阶段的子 Fiber 创建与 effectTag
  • completeWork:归阶段的 DOM 创建与 effectList
  • 双缓存机制:current / workInProgress 两棵 Fiber 树的切换
  • 原始出处:Lin Clark - A Cartoon Intro to Fiber - React Conf 2017
  • 文档
  • 前端

【免费下载链接】just-react

「React技术揭秘」 一本自顶向下的React源码分析书

项目地址:https://gitcode.com/gh_mirrors/ju/just-react
点击查看免费下载
上一篇:Fast-GitHub:彻底解决国内访问GitHub缓慢的终极方案
下一篇:彻底卸载Microsoft Edge终极指南:EdgeRemover脚本让你3分钟完成系统清理

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 8:00:57

ponytail插件怎么用?从命名隐喻到上手排查的完整指南

1. 从"ponytail"这个热搜词说起&#xff1a;它到底指什么第一次看到"ponytail"被当成技术关键词来搜&#xff0c;我其实愣了一下。这个词的字面意思是"马尾辫"&#xff0c;一个再日常不过的发型词汇&#xff0c;怎么会跟"skill""…

作者头像 李华
网站建设 2026/10/8 8:00:16

基于YOLO的深度学习头盔佩戴检测系统:从数据集到PyQt5部署全流程

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

作者头像 李华
网站建设 2026/10/8 7:59:58

MCP协议2026新规范解读:无状态架构重构与生产级安全防线

开篇先亮明我的立场&#xff1a;做 AI Agent 这块的朋友&#xff0c;最近要是还没听过 MCP&#xff0c;基本等于在圈子里暂时性失联。MCP 全称 Model Context Protocol&#xff0c;模型上下文协议&#xff0c;解决的是 Agent 如何标准化调用外部工具、读取外部数据、按统一语义…

作者头像 李华
网站建设 2026/10/8 7:59:17

压缩 PDF 免费的工具有哪些?网页、电脑、手机端工具整理

日常办公、提交材料经常会遇到 PDF 文件体积过大&#xff0c;邮箱发送失败、线上平台无法上传的情况。很多人到处找 PDF 压缩工具&#xff0c;又怕收费、带水印&#xff0c;或是隐私文件上传之后有泄露风险。今天整理了几款实用的免费 PDF 压缩工具&#xff0c;分为在线网页、电…

作者头像 李华
网站建设 2026/10/8 7:59:11

hyperframes:面向分布式数据管道的高性能共享内存数据帧交换解析

有段时间我在折腾大规模并行计算的数据通路&#xff0c;最头疼的就是数据在各个计算节点之间传来传去效率太低。CPU算得再快&#xff0c;数据搬不动&#xff0c;整个流水线照样卡脖子。后来我在一个开源社区的项目列表里看到了“hyperframes”这个名字&#xff0c;第一反应是“…

作者头像 李华