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 Fiber原理深入分析

    目录 为什么需要 fiber fiber 之前 fiber 之后 fiber 节点结构 dom 相关属性 tag key 和 type stateNode 链表树相关属性 副作用相关属性 flags Effect List 其他 lane alternate fiber 树的构建与更新 mount 过程 update 过程 总结 react16 版本之后引入了 fiber,整个架构层面的 调度.协调.diff 算法以及渲染等都与 fiber 密切相关.所以为了更好地讲解后面的内容,需要对 fib

  • 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中Redux核心原理深入分析

    目录 一.Redux是什么 二.Redux的核心思想 三.Redux中间件原理 四.手写一个Redux 总结 一.Redux是什么 众所周知,Redux最早运用于React框架中,是一个全局状态管理器.Redux解决了在开发过程中数据无限层层传递而引发的一系列问题,因此我们有必要来了解一下Redux到底是如何实现的? 二.Redux的核心思想 Redux主要分为几个部分:dispatch.reducer.state. 我们着重看下dispatch,该方法是Redux流程的第一步,在用户界面中通过

  • React Fiber与调和深入分析

    目录 一 引沿 二 什么是调和 三 什么是Filber 四 实现调和的过程 1. 创建FiberRoot 2. render阶段 五 总结 一 引沿 Fiber 架构是React16中引入的新概念,目的就是解决大型 React 应用卡顿,React在遍历更新每一个节点的时候都不是用的真实DOM,都是采用虚拟DOM,所以可以理解成fiber就是React的虚拟DOM,更新Fiber的过程叫做调和,每一个fiber都可以作为一个执行单元来处理,所以每一个 fiber 可以根据自身的过期时间expir

  • React懒加载实现原理深入分析

    目录 1.代码分割 2.React的懒加载 import() 原理 React.lazy 原理 Suspense 原理 小结 1.代码分割 (1)为什么要进行代码分割? 现在前端项目基本都采用打包技术,比如 Webpack,JS逻辑代码打包后会产生一个 bundle.js 文件,而随着我们引用的第三方库越来越多或业务逻辑代码越来越复杂,相应打包好的 bundle.js 文件体积就会越来越大,因为需要先请求加载资源之后,才会渲染页面,这就会严重影响到页面的首屏加载. 而为了解决这样的问题,避免大体

  • React Hooks核心原理深入分析讲解

    目录 Hooks 闭包 开始动手实现 将useState应用到组件中 过期闭包 模块模式 实现useEffect 支持多个Hooks Custom Hooks 重新理解Hooks规则 React Hooks已经推出一段时间,大家应该比较熟悉,或者多多少少在项目中用过.写这篇文章简单分析一下Hooks的原理,并带大家实现一个简易版的Hooks. 这篇写的比较细,相关的知识点都会解释,给大家刷新一下记忆. Hooks Hooks是React 16.8推出的新功能.以这种更简单的方式进行逻辑复用.之前

随机推荐