React Fiber原理深入分析

目录
  • 为什么需要 fiber
  • fiber 之前
  • fiber 之后
  • fiber 节点结构
  • dom 相关属性
    • tag
    • key 和 type
    • stateNode
  • 链表树相关属性
  • 副作用相关属性
    • flags
    • Effect List
    • 其他
    • lane
    • alternate
  • fiber 树的构建与更新
    • mount 过程
    • update 过程
  • 总结

react16 版本之后引入了 fiber,整个架构层面的 调度、协调、diff 算法以及渲染等都与 fiber 密切相关。所以为了更好地讲解后面的内容,需要对 fiber 有个比较清晰的认知。本章将介绍以下内容:

为什么需要 fiberfiber 节点结构中的属性fiber 树是如何构建与更新的

为什么需要 fiber

Lin Clark 在 React Conf 2017 的演讲中,他通过漫画的形式,很好地讲述了 fiber 为何出现,下面我根据她的演讲,结合我自己的理解来谈一谈 fiber 出现的原因。

fiber 之前

在 react15 及之前 fiber 未出现时,react 的一系列执行过程例如生命周期执行、虚拟 dom 的比较、dom 树的更新等都是同步的,一旦开始执行就不会中断,直到所有的工作流程全部结束为止。

要知道,react 所有的状态更新,都是从根组件开始的,当应用组件树比较庞大时,一旦状态开始变更,组件树层层递归开始更新,js 主线程就不得不停止其他工作。例如组件树一共有 1000 个组件需要更新,每个组件更新所需要的时间为 1s,那么在这 1s 内浏览器都无法做其他的事情,用户的点击输入等交互事件、页面动画等都不会得到响应,体验就会非常的差。

这种情况下,函数堆栈的调用就像下图一样,层级很深,很长时间不会返回

fiber 之后

为了解决这一问题,react 引入了 fiber 这种数据结构,将更新渲染耗时长的大任务,分为许多的小片。每个小片的任务执行完成后,都先去执行其他高优先级的任务(例如用户点击输入事件、动画等),这样 js 的主线程就不会被 react 独占,虽然任务执行的总时间不变,但是页面能够及时响应高优先级任务,显得不会卡顿了。

fiber 分片模式下,浏览器主线程能够定期被释放,保证了渲染的帧率,函数的堆栈调用如下(波谷表示执行分片任务,波峰表示执行其他高优先级任务):

react 通过 fiber,为我们提供了一种跟踪、调度、暂停和中止工作的便捷方式,保证了页面的性能和流畅度。

fiber 节点结构

fiber 是一种数据结构,每个 fiber 节点的内部,都保存了 dom 相关信息、fiber 树相关的引用、要更新时的副作用等,我们可以看一下源码中的 fiber 结构:

// packages/react-reconciler/src/ReactInternalTypes.jsexport type Fiber = {|  // 作为静态数据结构,存储节点 dom 相关信息  tag: WorkTag, // 组件的类型,取决于 react 的元素类型  key: null | string,  elementType: any, // 元素类型  type: any, // 定义与此fiber关联的功能或类。对于组件,它指向构造函数;对于DOM元素,它指定HTML tag  stateNode: any, // 真实 dom 节点  // fiber 链表树相关  return: Fiber | null, // 父 fiber  child: Fiber | null, // 第一个子 fiber  sibling: Fiber | null, // 下一个兄弟 fiber  index: number, // 在父 fiber 下面的子 fiber 中的下标  ref:    | null    | (((handle: mixed) => void) & {_stringRef: ?string, ...})    | RefObject,  // 工作单元,用于计算 state 和 props 渲染  pendingProps: any, // 本次渲染需要使用的 props  memoizedProps: any, // 上次渲染使用的 props  updateQueue: mixed, // 用于状态更新、回调函数、DOM更新的队列  memoizedState: any, // 上次渲染后的 state 状态  dependencies: Dependencies | null, // contexts、events 等依赖  mode: TypeOfMode,  // 副作用相关  flags: Flags, // 记录更新时当前 fiber 的副作用(删除、更新、替换等)状态  subtreeFlags: Flags, // 当前子树的副作用状态  deletions: Array<Fiber> | null, // 要删除的子 fiber  nextEffect: Fiber | null, // 下一个有副作用的 fiber  firstEffect: Fiber | null, // 指向第一个有副作用的 fiber  lastEffect: Fiber | null, // 指向最后一个有副作用的 fiber   // 优先级相关  lanes: Lanes,  childLanes: Lanes,  alternate: Fiber | null, // 指向 workInProgress fiber 树中对应的节点  actualDuration?: number,  actualStartTime?: number,  selfBaseDuration?: number,  treeBaseDuration?: number,  _debugID?: number,  _debugSource?: Source | null,  _debugOwner?: Fiber | null,  _debugIsCurrentlyTiming?: boolean,  _debugNeedsRemount?: boolean,  _debugHookTypes?: Array<HookType> | null,|};

dom 相关属性

fiber 中和 dom 节点相关的信息主要关注 tagkeytypestateNode

tag

fiber 中 tag 属性的 ts 类型为 workType,用于标记不同的 react 组件类型,我们可以看一下源码中 workType 的枚举值:

// packages/react-reconciler/src/ReactWorkTags.jsexport const FunctionComponent = 0;export const ClassComponent = 1;export const IndeterminateComponent = 2; // Before we know whether it is function or classexport const HostRoot = 3; // Root of a host tree. Could be nested inside another node.export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.export const HostComponent = 5;export const HostText = 6;export const Fragment = 7;export const Mode = 8;export const ContextConsumer = 9;export const ContextProvider = 10;export const ForwardRef = 11;export const Profiler = 12;export const SuspenseComponent = 13;export const MemoComponent = 14;export const SimpleMemoComponent = 15;export const LazyComponent = 16;export const IncompleteClassComponent = 17;export const DehydratedFragment = 18;export const SuspenseListComponent = 19;export const FundamentalComponent = 20;export const ScopeComponent = 21;export const Block = 22;export const OffscreenComponent = 23;export const LegacyHiddenComponent = 24;

在 react 协调时,beginWork 和 completeWork 等流程时,都会根据 tag 类型的不同,去执行不同的函数处理 fiber 节点。

key 和 type

keytype 两项用于 react diff 过程中确定 fiber 是否可以复用。

key 为用户定义的唯一值。type 定义与此fiber关联的功能或类。对于组件,它指向函数或者类本身;对于DOM元素,它指定HTML tag。

stateNode

stateNode用于记录当前fiber所对应的真实dom节点或者当前虚拟组件的实例,这么做的原因第一是为了实现Ref,第二是为了实现真实 dom的跟踪。

链表树相关属性

我们看一下和 fiber 链表树构建相关的 returnchildsibling 几个字段:

return:指向父 fiber,若没有父 fiber 则为 null

child: 指向第一个子 fiber,若没有任何子 fiber 则为 null

sibling:指向下一个兄弟 fiber,若没有下一个兄弟 fiber 则为 null

通过这几个字段,各个 fiber 节点构成了 fiber 链表树结构:

副作用相关属性

首先理解一下 react 中的副作用,举一个生活中比较通俗的例子:我们感冒了本来吃点药就没事了,但是吃了药发现身体过敏了,而这个“过敏”就是副作用。react 中,我们修改了 state、props、ref 等数据,除了数据改变之外,还会引起 dom 的变化,这种 render 阶段不能完成的工作,我们称之为副作用。相关参考视频讲解:进入学习

flags

react 中通过 flags 记录每个节点diff后需要变更的状态,例如 dom 的添加、替换、删除等等。我们可以看一下源码中 Flags 枚举类型:

例如 Deletion 代表更新时要对 dom 进行删除,Placement 代表要进行添加或者替换等等。

// packages/react-reconciler/src/ReactFiberFlags.jsexport type Flags = number;export const NoFlags = /*                      */ 0b000000000000000000;export const PerformedWork = /*                */ 0b000000000000000001;export const Placement = /*                    */ 0b000000000000000010;export const Update = /*                       */ 0b000000000000000100;export const PlacementAndUpdate = /*           */ 0b000000000000000110;export const Deletion = /*                     */ 0b000000000000001000;export const ContentReset = /*                 */ 0b000000000000010000;export const Callback = /*                     */ 0b000000000000100000;export const DidCapture = /*                   */ 0b000000000001000000;export const Ref = /*                          */ 0b000000000010000000;export const Snapshot = /*                     */ 0b000000000100000000;export const Passive = /*                      */ 0b000000001000000000;export const PassiveUnmountPendingDev = /*     */ 0b000010000000000000;export const Hydrating = /*                    */ 0b000000010000000000;export const HydratingAndUpdate = /*           */ 0b000000010000000100;export const LifecycleEffectMask = /*          */ 0b000000001110100100;export const HostEffectMask = /*               */ 0b000000011111111111;export const Incomplete = /*                   */ 0b000000100000000000;export const ShouldCapture = /*                */ 0b000001000000000000;export const ForceUpdateForLegacySuspense = /* */ 0b000100000000000000;export const PassiveStatic = /*                */ 0b001000000000000000;export const BeforeMutationMask = /*           */ 0b000000001100001010;export const MutationMask = /*                 */ 0b000000010010011110;export const LayoutMask = /*                   */ 0b000000000010100100;export const PassiveMask = /*                  */ 0b000000001000001000;export const StaticMask = /*                   */ 0b001000000000000000;export const MountLayoutDev = /*               */ 0b010000000000000000;export const MountPassiveDev = /*              */ 0b100000000000000000;

Effect List

在 render 阶段时,react 会采用深度优先遍历,对 fiber 树进行遍历,把每一个有副作用的 fiber 筛选出来,最后构建生成一个只带副作用的 Effect list 链表。和该链表相关的字段有 firstEffectnextEffectlastEffect

firstEffect 指向第一个有副作用的 fiber 节点,lastEffect 指向最后一个有副作用的节点,中间的节点全部通过 nextEffect 链接,最终形成 Effect 链表。

在 commit 阶段,React 拿到 Effect list 链表中的数据后,根据每一个 fiber 节点的 flags 类型,对相应的 DOM 进行更改。

其他

其他需要重点关注一下的属性还有 lanealternate

lane

lane 代表 react 要执行的 fiber 任务的优先级,通过这个字段,render 阶段 react 确定应该优先将哪些任务提交到 commit 阶段去执行。

我们看一下源码中 lane 的枚举值:

// packages/react-reconciler/src/ReactFiberLane.jsInputDiscreteHydrationLane: Lane = /*                   */ 0b0000000000000000000000000000100;const InputDiscreteLanes: Lanes = /*                    */ 0b0000000000000000000000000011000;const InputContinuousHydrationLane: Lane = /*           */ 0b0000000000000000000000000100000;const InputContinuousLanes: Lanes = /*                  */ 0b0000000000000000000000011000000;export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000100000000;export const DefaultLanes: Lanes = /*                   */ 0b0000000000000000000111000000000;const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000001000000000000;const TransitionLanes: Lanes = /*                       */ 0b0000000001111111110000000000000;const RetryLanes: Lanes = /*                            */ 0b0000011110000000000000000000000;export const SomeRetryLane: Lanes = /*                  */ 0b0000010000000000000000000000000;export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;const NonIdleLanes = /*                                 */ 0b0000111111111111111111111111111;export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;const IdleLanes: Lanes = /*                             */ 0b0110000000000000000000000000000;export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;

同 Flags 的枚举值一样,Lanes 也是用 31 位的二进制数表示,表示了 31 条赛道,位数越小的赛道,代表的优先级越高。

例如 InputDiscreteHydrationLaneInputDiscreteLanesInputContinuousHydrationLane 等用户交互引起的更新的优先级较高,DefaultLanes 这种请求数据引起更新的优先级中等,而 OffscreenLaneIdleLanes 这种优先级较低。

优先级越低的任务,在 render 阶段越容易被打断,commit 执行的时机越靠后。

alternate

当 react 的状态发生更新时,当前页面所对应的 fiber 树称为 current Fiber,同时 react 会根据新的状态构建一颗新的 fiber 树,称为 workInProgress Fiber。current Fiber 中每个 fiber 节点通过 alternate 字段,指向 workInProgress Fiber 中对应的 fiber 节点。同样 workInProgress Fiber 中的 fiber

节点的 alternate 字段也会指向 current Fiber 中对应的 fiber 节点。

fiber 树的构建与更新

下面我们结合源码,来看一下实际工作过程中 fiber 树的构建与更新过程。

mount 过程

react 首次 mount 开始执行时,以 ReactDOM.render 为入口函数,会经过如下一系列的函数调用:ReactDOM.render ——> legacyRenderSubtreeIntoContainer ——> legacyCreateRootFromDOMContainer ——> createLegacyRoot ——> ReactDOMBlockingRoot ——> ReactDOMRoot ——> createRootImpl ——> createContainer ——> createFiberRoot ——> createHostRootFiber ——> createFiber

createFiber 函数中,调用 FiberNode 构造函数,创建了 rootFiber,它是 react 应用的根 fiber:

// packages/react-reconciler/src/ReactFiber.old.jsconst createFiber = function(  tag: WorkTag,  pendingProps: mixed,  key: null | string,  mode: TypeOfMode,): Fiber {  return new FiberNode(tag, pendingProps, key, mode);};

createFiberRoot 函数中,调用 FiberRootNode 构造函数,创建了 fiberRoot,它指向真实根 dom 节点。

// packages/react-reconciler/src/ReactFiberRoot.old.jsexport function createFiberRoot(  containerInfo: any,  tag: RootTag,  hydrate: boolean,  hydrationCallbacks: null | SuspenseHydrationCallbacks,): FiberRoot {  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);  if (enableSuspenseCallback) {    root.hydrationCallbacks = hydrationCallbacks;  }  const uninitializedFiber = createHostRootFiber(tag);  root.current = uninitializedFiber;  uninitializedFiber.stateNode = root;  initializeUpdateQueue(uninitializedFiber);  return root;}

另外 createFiberRoot 函数中,还让 rootFiber 的 stateNode 字段指向了 fiberRoot,fiberRoot 的 current 字段指向了 rootFiber。从而一颗最原始的 fiber 树根节点就创建完成了:

上面的 rootFiber 和 fiberRoot 创建完成后,react 就会根据 jsx 的内容去创建详细的 dom 树了,例如有如下的 jsx:

<div id="root">  <div id="a1">    <div id="b1">      <div id="c1">        <div id="d1"></div>        <div id="d2"></div>        <div id="d3"></div>      </div>      <div id="c2"></div>    </div>  </div></div>

react 对于 fiber 结构的创建和更新,都是采用深度优先遍历,从 rootFiber(此处对应id为root的节点)开始,首先创建 child a1,然后发现 a1 有子节点 b1,继续对 b1 进行遍历,b1 有子节点 c1,再去创建 c1 的子节点 d1、d2、d3,直至发现 d1、d2、d3 都没有子节点来了,再回去创建 c2.

上面的过程,每个节点开始创建时,执行 beginWork 流程,直至该节点的所有子孙节点都创建(更新)完成后,执行 completeWork 流程,过程的图示如下:

update 过程

update 时,react 会根据新的 jsx 内容创建新的 workInProgress fiber,还是通过深度优先遍历,对发生改变的 fiber 打上不同的 flags 副作用标签,并通过 firstEffectnextEffect 等字段形成 Effect List 链表。

例如上面的 jsx 结构,发生了如下的更新:

<div id="root">  <div id="a1">    <div id="b1">      <div id="c1">        <div id="d1"></div>-       <div id="d2"></div>-       <div id="d3"></div>      </div>-     <div id="c2"></div>+     <div id="c2">new content</div>    </div>  </div></div>

react 会根据新的 jsx 解析后的内容,调用 createWorkInProgress 函数创建 workInProgress fiber,对其标记副作用:

// packages/react-reconciler/src/ReactFiber.old.jsexport function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {  let workInProgress = current.alternate;  if (workInProgress === null) { // 区分 mount 还是 update    workInProgress = createFiber(      current.tag,      pendingProps,      current.key,      current.mode,    );    workInProgress.elementType = current.elementType;    workInProgress.type = current.type;    workInProgress.stateNode = current.stateNode;    if (__DEV__) {      workInProgress._debugID = current._debugID;      workInProgress._debugSource = current._debugSource;      workInProgress._debugOwner = current._debugOwner;      workInProgress._debugHookTypes = current._debugHookTypes;    }    workInProgress.alternate = current;    current.alternate = workInProgress;  } else {    workInProgress.pendingProps = pendingProps;    workInProgress.type = current.type;    workInProgress.subtreeFlags = NoFlags;    workInProgress.deletions = null;    if (enableProfilerTimer) {      workInProgress.actualDuration = 0;      workInProgress.actualStartTime = -1;    }  }  // 重置所有的副作用  workInProgress.flags = current.flags & StaticMask;  workInProgress.childLanes = current.childLanes;  workInProgress.lanes = current.lanes;  workInProgress.child = current.child;  workInProgress.memoizedProps = current.memoizedProps;  workInProgress.memoizedState = current.memoizedState;  workInProgress.updateQueue = current.updateQueue;  // 克隆依赖  const currentDependencies = current.dependencies;  workInProgress.dependencies =    currentDependencies === null      ? null      : {          lanes: currentDependencies.lanes,          firstContext: currentDependencies.firstContext,        };  workInProgress.sibling = current.sibling;  workInProgress.index = current.index;  workInProgress.ref = current.ref;  if (enableProfilerTimer) {    workInProgress.selfBaseDuration = current.selfBaseDuration;    workInProgress.treeBaseDuration = current.treeBaseDuration;  }  if (__DEV__) {    workInProgress._debugNeedsRemount = current._debugNeedsRemount;    switch (workInProgress.tag) {      case IndeterminateComponent:      case FunctionComponent:      case SimpleMemoComponent:        workInProgress.type = resolveFunctionForHotReloading(current.type);        break;      case ClassComponent:        workInProgress.type = resolveClassForHotReloading(current.type);        break;      case ForwardRef:        workInProgress.type = resolveForwardRefForHotReloading(current.type);        break;      default:        break;    }  }  return workInProgress;}

最终生成的 workInProgress fiber 图示如下:

然后如上面所说,current fiber 和 workInProgress fiber 中对应的 alternate 会相互指向,然后 workInProgress fiber 完全创建完成后,fiberRoot 的 current 字段的指向会从 current fiber 中的 rootFiber 改为 workInProgress fiber 中的 rootFiber:

总结

本章讲解了 fiber 出现的主要原因、fiber 节点中主要的属性以及 fiber 树是如何构建与更新的。

到此这篇关于React Fiber原理深入分析的文章就介绍到这了,更多相关React Fiber内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解React Fiber架构原理

    目录 一.概述 二.Fiber架构 2.1 执行单元 2.2 数据结构 2.3 Fiber链表结构 2.4 Fiber节点 2.5 API 2.5.1 requestAnimationFrame 2.5.2 requestIdleCallback 三.Fiber执行流程 3.1 render阶段 3.1.1 遍历流程 3.1.2 收集effect list 3.2 commit阶段 3.2.1 根据effect list 更新视图 3.2.2 视图更新 四.总结 一.概述 在 React 16

  • React Diff原理深入分析

    在了解Diff前,先看下React的虚拟DOM的结构 这是html结构 <div id="father"> <p class="child">I am child p</p> <div class="child">I am child div</div> </div> 这是React渲染html时的js代码   自己可以在babel上试试 React.createElemen

  • React Fiber源码深入分析

    目录 前言 React架构前世今生 React@15及之前 React@16及之后 Fiber Fiber简单理解 Fiber结构 Fiber工作原理 mount update 前言 本次React源码参考版本为17.0.3. React架构前世今生 查阅文档了解到, React@16.x是个分水岭. React@15及之前 在16之前,React架构大致可以分为两层: Reconciler: 主要职责是对比查找更新前后的变化的组件: Renderer: 主要职责是基于变化渲染页面: 但是Rea

  • 详解React Fiber的工作原理

    啥是React Fiber? React Fiber,简单来说就是一个从React v16开始引入的新协调引擎,用来实现Virtual DOM的增量渲染. 说人话:就是一种能让React视图更新过程变得更加流畅顺滑的处理手法. 我们都知道:进程大,线程小.而Fiber(纤维)是一种比线程还要细粒度的处理机制.从这个单词也可以猜测:React Fiber会很"细".到底怎么个细法,我们接着往下看. 为什么会有React Fiber? 之前说了,React Fiber是为了让React的视

  • react fiber执行原理示例解析

    目录 为什么要使用fiber,要解决什么问题? fiber是什么? 数据结构 执行单元 浏览器工作: Fiber执行原理 workInProgress tree: currentFiber tree: Effects list: render阶段: 遍历节点过程: 收集effect list: commit阶段: 为什么commit必须是同步的操作的? 为什么要使用fiber,要解决什么问题? 在 react16 引入 Fiber 架构之前,react 会采用递归方法对比两颗虚拟DOM树,找出需

  • React Fiber 链表操作及原理示例详解

    目录 正文 什么是Fiber Fiber节点React源码 Fiber树是链表 节点独立 节省操作时间与单向操作 利于双缓存与异步可中断更新操作 异步可中断更新 双缓存 正文 看了React源码之后相信大家都会对Fiber有自己不同的见解,而我对Fiber最大的见解就是这玩意儿就是个链表.如果把整个Fiber树当成一个整体确实有点难理解源码,但是如果把它拆开了,将每个节点都看成一个独立单元却能得到一个很清晰的思路,接下来我就简单几点讲讲,我所认为的为什么React要用链表这种数据结构来构建Fib

  • 深入理解React State 原理

    目录 问题:setState 到底是同步还是异步的? 类组件state setState原理揭秘 函数组件state 问题:setState 到底是同步还是异步的? 如果对 React 底层有一定了解,可以回答出 batchUpdate 批量更新概念,以及批量更新被打破的条件. 答案:有时是同步,有时是异步. 在 合成事件 和 生命周期函数 里是 异步 的在 原生事件 和 setTimeout.promise里是 同步 的 造成setState的异步并不是由内部的异步代码引起的,在本身的执行过程

  • SQL查询的底层运行原理深入分析

    前言 SQL 语言无处不在.SQL 已经不仅仅是技术人员的专属技能了,似乎人人都会写SQL,就如同人人都是产品经理一样.如果你是做后台开发的,那么CRUD就是家常便饭.如果你是做数仓开发的,那么写SQL可能占据了你的大部分工作时间.我们在理解 SELECT 语法的时候,还需要了解 SELECT 执行时的底层原理.只有这样,才能让我们对 SQL 有更深刻的认识.本文分享将逐步分解SQL的执行过程,希望对你有所帮助. 数据准备 本文旨在说明SQL查询的执行过程,不会涉及太复杂的SQL操作,主要涉及两

  • React Fiber结构的创建步骤

    React Fiber的创建 当前React版本基于V17.0.2版本,本篇主要介绍fiber结构的创建. 一.开始之前 个人理解,如有不对,请指出. 首先需要配置好React的debugger开发环境,入口在这里:github 执行npm run i,安装依赖,npm start运行环境. 二.从React.render开始 通过在项目入口处调用React.render,打上Debug,查看React调用栈. const root = document.getElementById('root

  • Spring底层原理深入分析

    目录 bean生命周期 推断构造方法的底层原理 1.使用哪个构造方法 2.如果有参把哪个bean对象赋值给入参 AOP实现原理 spring事务 @Configuration 循环依赖 为什么会出现循环依赖 提前AOP 第一级缓存singletonObjects 第二级缓存earlySingletonObjects 第三级缓存singletonFactories bean生命周期 userService.class--->推断构造方法--->对象--->依赖注入--->初始化前(@

随机推荐

其他