react源码中的fiber架构

先看一下FiberNode在源码中的样子

FiberNode

// packages/react-reconciler/src/ReactFiber.old.js
function FiberNode(
 tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,
) {
 // Instance
 this.tag = tag;
 this.key = key;
 this.elementType = null;
 this.type = null;
 this.stateNode = null;
 // 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;
 // Effects
 this.flags = NoFlags;
 this.nextEffect = null;
 this.firstEffect = null;
 this.lastEffect = null;
 this.lanes = NoLanes;
 this.childLanes = NoLanes;
 this.alternate = null;
 if (enableProfilerTimer) {
 // Note: The following is done to avoid a v8 performance cliff.
 //
 // Initializing the fields below to smis and later updating them with
 // double values will cause Fibers to end up having separate shapes.
 // This behavior/bug has something to do with Object.preventExtension().
 // Fortunately this only impacts DEV builds.
 // Unfortunately it makes React unusably slow for some applications.
 // To work around this, initialize the fields below with doubles.
 //
 // Learn more about this here:
 // https://github.com/facebook/react/issues/14365
 // https://bugs.chromium.org/p/v8/issues/detail?id=8538
 this.actualDuration = Number.NaN;
 this.actualStartTime = Number.NaN;
 this.selfBaseDuration = Number.NaN;
 this.treeBaseDuration = Number.NaN;
 // It's okay to replace the initial doubles with smis after initialization.
 // This won't trigger the performance cliff mentioned above,
 // and it simplifies other profiler code (including DevTools).
 this.actualDuration = 0;
 this.actualStartTime = -1;
 this.selfBaseDuration = 0;
 this.treeBaseDuration = 0;
 }
 if (__DEV__) {
 // This isn't directly used but is handy for debugging internals:
 ...
 }
}
  • 我们看FiberNode这个构造函数里面只是赋值,我们再找一下链路上的Fiber,我们发现在函数createFiber的返回值类型里面出现了Fiber类型,所以
// packages/react-reconciler/src/ReactInternalTypes.js
export type Fiber = {|
 // These first fields are conceptually members of an Instance. This used to
 // be split into a separate type and intersected with the other Fiber fields,
 // but until Flow fixes its intersection bugs, we've merged them into a
 // single type.
 // An Instance is shared between all versions of a component. We can easily
 // break this out into a separate object to avoid copying so much to the
 // alternate versions of the tree. We put this on a single object for now to
 // minimize the number of objects created during the initial render.
 // dom节点的相关信息
 tag: WorkTag,// 组件的类型
 key: null | string, // 唯一值
 elementType: any,// 元素类型
 // 判定fiber节点的类型,用于diff
 type: any,
 // 真实 dom 节点
 stateNode: any,
 // Conceptual aliases
 // parent : Instance -> return The parent happens to be the same as the
 // return fiber since we've merged the fiber and instance.
 // Remaining fields belong to Fiber
 // fiber 链表树
 return: Fiber | null, // 父 fiber
 child: Fiber | null, // 第一个子 fiber
 sibling: Fiber | null, // 下一个兄弟 fiber
 index: number, // 在父 fiber 下面的子 fiber 中的下标
 // The ref last used to attach this node.
 // I'll avoid adding an owner field for prod and model that as functions.
 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 等依赖
 // Bitfield that describes properties about the fiber and its subtree. E.g.
 // the ConcurrentMode flag indicates whether the subtree should be async-by-
 // default. When a fiber is created, it inherits the mode of its
 // parent. Additional flags can be set at creation time, but after that the
 // value should remain unchanged throughout the fiber's lifetime, particularly
 // before its child fibers are created.
 mode: TypeOfMode,
 // Effect
 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,
 // This is a pooled version of a Fiber. Every fiber that gets updated will
 // eventually have a pair. There are cases when we can clean up pairs to save
 // memory if we need to.
 alternate: Fiber | null,// 指向 workInProgress fiber 树中对应的节点
 // Time spent rendering this Fiber and its descendants for the current update.
 // This tells us how well the tree makes use of sCU for memoization.
 // It is reset to 0 each time we render and only updated when we don't bailout.
 // This field is only set when the enableProfilerTimer flag is enabled.
 actualDuration?: number,
 // If the Fiber is currently active in the "render" phase,
 // This marks the time at which the work began.
 // This field is only set when the enableProfilerTimer flag is enabled.
 actualStartTime?: number,
 // Duration of the most recent render time for this Fiber.
 // This value is not updated when we bailout for memoization purposes.
 // This field is only set when the enableProfilerTimer flag is enabled.
 selfBaseDuration?: number,
 // Sum of base times for all descendants of this Fiber.
 // This value bubbles up during the "complete" phase.
 // This field is only set when the enableProfilerTimer flag is enabled.
 treeBaseDuration?: number,
 // Conceptual aliases
 // workInProgress : Fiber -> alternate The alternate used for reuse happens
 // to be the same as work in progress.
 // __DEV__ only
 _debugID?: number,
 _debugSource?: Source | null,
 _debugOwner?: Fiber | null,
 _debugIsCurrentlyTiming?: boolean,
 _debugNeedsRemount?: boolean,
 // Used to verify that the order of hooks does not change between renders.
 _debugHookTypes?: Array<HookType> | null,
|};
  • 整个fiber架构看起来可以分为dom信息、副作用、优先级、链表树等几个模块,那我们依次来拆分一下

dom信息节点

tag: WorkTag
我们看到这个`tag`为`WorkTag`类型,用来区分`React`组件的类型
// packages/react-reconciler/src/ReactWorkTags.js
export type WorkTag =
 | 0
 | 1
 | 2
 | 3
 | 4
 | 5
 | 6
 | 7
 | 8
 | 9
 | 10
 | 11
 | 12
 | 13
 | 14
 | 15
 | 16
 | 17
 | 18
 | 19
 | 20
 | 21
 | 22
 | 23
 | 24;
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export 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;

相关参考视频讲解:进入学习

上述代码,区分了组件的类型,在后期协调阶段beginWorkcompleteWork的流程里根据不同的类型组件去做不同的fiber节点的处理

key: null | string、type: any
key为唯一值,type为与fiber关联的节点类型,都用于beginWork流程里面的reconcileChildren流程
elementType
元素类型
stateNode
- stateNode 用于记录当前 fiber 所对应的真实 dom 节点或者当前虚拟组件的实例。
- 便于实现Ref
- 便于追踪Rdom

fiber 链表树

fiber链表树里面有四个字段returnchildsiblingindex

  • return:指向父节点,没有父节点则为null。
  • child:指向下一个子节点,没有下一个子节点则为null。
  • sibling:指向兄弟节点,没有下一个兄弟节点则为null。
  • index:父fiber下面的子fiber下标
    通过这些字段那么我们可以形成一个闭环链表,举个栗子。
<div className='box'>
 <h1 className='title' style={{'color':'red'}}>React源码解析</h1>
 <ul>
 <li>第一章</li>
 <li>第二章</li>
 <li>第三章</li>
 <li>第四章</li>
 </ul>
</div>

根据上面的代码所对应的fiber链表树结构就是:

副作用相关

所谓副作用就是一套流程中我们不期望发生的情况。举个通俗的例子就是我们生活中去学游泳,在学会游泳的过程中呛了几口水,这个呛了几口水相对于成功学会游泳来说就是副作用,回归到react代码中,我们通过某些手段去修改propsstate等数据,数据修改完毕之后,但是同时引起了dom不必要的变化,那么这个变化就是副作用,当然这个副作用是必然存在的,就像游泳一样,必然会呛几口水,哈哈。

flags: Flags
记录当前节点通过`reconcileChildren`之后的的副作用,如插入,删除等

例如Placement,表示插入,也叫新增。Deletion表示删除,Update表示更新

export type Flags = number;
// Don't change these two values. They're used by React Dev Tools.
export const NoFlags = /* */ 0b000000000000000000;
export const PerformedWork = /* */ 0b000000000000000001;
// You can change the rest (and add more).
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;
// TODO (effects) Remove this bit once the new reconciler is synced to the old.
export const PassiveUnmountPendingDev = /* */ 0b000010000000000000;
export const Hydrating = /* */ 0b000000010000000000;
export const HydratingAndUpdate = /* */ 0b000000010000000100;
// Passive & Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b000000001110100100;
// Union of all host effects
export const HostEffectMask = /* */ 0b000000011111111111;
// These are not really side effects, but we still reuse this field.
export const Incomplete = /* */ 0b000000100000000000;
export const ShouldCapture = /* */ 0b000001000000000000;
export const ForceUpdateForLegacySuspense = /* */ 0b000100000000000000;
// Static tags describe aspects of a fiber that are not specific to a render,
// e.g. a fiber uses a passive effect (even if there are no updates on this particular render).
// This enables us to defer more work in the unmount case,
// since we can defer traversing the tree during layout to look for Passive effects,
// and instead rely on the static flag as a signal that there may be cleanup work.
export const PassiveStatic = /* */ 0b001000000000000000;
// Union of side effect groupings as pertains to subtreeFlags
export const BeforeMutationMask = /* */ 0b000000001100001010;
export const MutationMask = /* */ 0b000000010010011110;
export const LayoutMask = /* */ 0b000000000010100100;
export const PassiveMask = /* */ 0b000000001000001000;
// Union of tags that don't get reset on clones.
// This allows certain concepts to persist without recalculting them,
// e.g. whether a subtree contains passive effects or portals.
export const StaticMask = /* */ 0b001000000000000000;
// These flags allow us to traverse to fibers that have effects on mount
// without traversing the entire tree after every commit for
// double invoking
export const MountLayoutDev = /* */ 0b010000000000000000;
export const MountPassiveDev = /* */ 0b100000000000000000;

当然副作用不仅仅只是一个,所以React中在render阶段中采用的是深度遍历的策略去找出当前fiber树中所有的副作用,并维护一个副作用链表EffectList,与链表相关的字段还有firstEffectnextEffectlastEffect 我们来画一张图来简略示意一下。

解读一下就是,fristEffect指向第一个有副作用的fiber节点,lastEffect指向最后一个具有副作用的fiber节点,中间都是用nextEffect链接,这样组成了一个单向链表。
render阶段里面这一段处理就完了,在后面的commit阶段里面,React会根据EffectList里面fiber节点的副作用,会对应的处理相应的DOM,然后生成无副作用的虚拟节点,进行真实dom的创建。

优先级相关

当然React作为一个庞大的框架,肯定有自己的一套关于渲染的优先级机制,不然全都是一股脑按部就班的走,那肯定不行哒。

那么优先级我们就要关注一下lanealternateReact中每个fiber任务都有自己的lane(执行优先级),这样在render阶段react才知道,应该优先把哪个fiber任务提交到commit阶段去执行。而alternate是在render阶段中用来做为指针的,什么意思?React在状态发生改变的时候,就会根据当前的页面结构,生成两棵fiber树,一棵老的称之为current Fiber,而另一棵将要生成新的页面的树叫做workInProgress Fiber,而alternate作为指针,就是把current Fiber中的每一个节点指向workInProgress Fiber中的每一个节点。同样的他也会从workInProgress Fiber中指向 current Fiber

我们了解到了alternate,那就来说一说这个lane吧。

// packages/react-reconciler/src/ReactFiberLane.js
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;
export const InputDiscreteHydrationLane: 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;

可见这个lane也是用31位二进制表示的唯一值,来进行优先级的判定的,并且位数越低,则优先级越大。

Props && State相关

pendingProps: any
本次渲染需要使用的 props 
memoizedProps: any
上次渲染使用的 props 
updateQueue: mixed
用于状态更新、回调函数、DOM更新的队列
memoizedState: any
上次渲染后的 state 状态 
dependencies: Dependencies | null
contexts、events 等依赖

Fiber树的创建与更新的流程

上面一部分讲了React Fiber的基本架构,从真实dom信息副作用优先级等方面看了一下,为后面的render阶段协调调度以及commit阶段打下基础,那么接下来我们去探讨一下new FiberNode之后得到的什么样的rootFiber
我们在第二章节里面提到了整个的创建过程# React源码解析系列(二) -- 初始化组件的创建更新流程,那么这里深入探讨一下createFiber,在这个函数里面new FiberNode,创建了rootFiber,他也就是整个React应用的的根fiber。并且在createFiberRoot里面new FiberRootNode,创建了fiberRoot,它便是指向真实dom的根节点。所以在# React源码解析系列(二) -- 初始化组件的创建更新流程中我强调了root.currentuninitializedFiber.stateNode这两个东西,也就是这里说的rootFiberstateNode字段指向了 fiberRoot,并且fiberRoot的current指向了rootFiber,具体的示例图如下:

所以这里就完成了fiber树根节点的创建了。

拿到了上面创建完成的rootFiberfiberRoot之后那么我们接下来就是去根据我们的组件jsx去创建详细的dom树了,举个例子:

<div className='box'>
 <h1 className='title' style={{'color':'red'}}>React源码解析</h1>
 <ul>
 <li>第一章</li>
 <li>第二章</li>
 <li>第三章</li>
 <li>第四章</li>
 </ul>
</div>

现有上面的jsx,那么我们创建dom树的形式是深度优先遍历,已beginworkcompletework表示一个节点的创建过程,流程如下:

上面的图说明了,在初始化的时候我们的dom树是怎么被创建出来的,那么在状态发生改变的时候,我们会根据当前新的jsx内容创建新的workInProgress fiber,我们以新的jsx为例:

<div className='box'>
<h1 className='title' style={{'color':'red'}}>React源码解析</h1>
+ <h1 className='title' style={{'color':'red'}}>React源码解析系列</h1>
 <ul>
 <li>第一章</li>
 <li>第二章</li>
 <li>第三章</li>
- <li>第四章</li>
 </ul>
+ <p>总结</p>
</div>

上面的jsx表示,更改了h1的内容,删除了第四章,增加了总结这几个操作,那么react根据当前新的jsx调用createWorkInProgress方法创建workInProgress fiber,那么我们先去看一下createWorkInProgress的源码实现。

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
 let workInProgress = current.alternate;
 if (workInProgress === null) { // null为初始化,否为update
 // We use a double buffering pooling technique because we know that we'll
 // only ever need at most two versions of a tree. We pool the "other" unused
 // node that we're free to reuse. This is lazily created to avoid allocating
 // extra objects for things that are never updated. It also allow us to
 // reclaim the extra memory if needed.
 workInProgress = createFiber(
 current.tag,
 pendingProps,
 current.key,
 current.mode,
 );
 workInProgress.elementType = current.elementType;
 workInProgress.type = current.type;
 workInProgress.stateNode = current.stateNode;
 if (__DEV__) {
 // DEV-only fields
 workInProgress._debugID = current._debugID;
 workInProgress._debugSource = current._debugSource;
 workInProgress._debugOwner = current._debugOwner;
 workInProgress._debugHookTypes = current._debugHookTypes;
 }
 // current 指向 workInProgress
 workInProgress.alternate = current;
 // workInProgress 指向 current
 current.alternate = workInProgress;
 } else {
 // 上一次的props
 workInProgress.pendingProps = pendingProps;
 // Needed because Blocks store data on type.
 workInProgress.type = current.type;
 // We already have an alternate.
 // Reset the effect tag.
 //清除flags
 workInProgress.flags = NoFlags;
 // The effect list is no longer valid.
 workInProgress.nextEffect = null;
 workInProgress.firstEffect = null;
 workInProgress.lastEffect = null;
 if (enableProfilerTimer) {
 // We intentionally reset, rather than copy, actualDuration & actualStartTime.
 // This prevents time from endlessly accumulating in new commits.
 // This has the downside of resetting values for different priority renders,
 // But works for yielding (the common case) and should support resuming.
 workInProgress.actualDuration = 0;
 workInProgress.actualStartTime = -1;
 }
 }
 // 绑定挂载的子fiber节点优先级、状态、props
 workInProgress.childLanes = current.childLanes;
 workInProgress.lanes = current.lanes;
 workInProgress.child = current.child;
 workInProgress.memoizedProps = current.memoizedProps;
 workInProgress.memoizedState = current.memoizedState;
 workInProgress.updateQueue = current.updateQueue;
 // Clone the dependencies object. This is mutated during the render phase, so
 // it cannot be shared with the current fiber.
 const currentDependencies = current.dependencies;
 workInProgress.dependencies =
 currentDependencies === null
 ? null
 : {
 lanes: currentDependencies.lanes,
 firstContext: currentDependencies.firstContext,
 };
 // These will be overridden during the parent's reconciliation
 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;
}

并且为其标记副作用,具体如下:

而前面所说的alternate在这里相互指向,其实也就是在reconciler阶段起到了复用节点的作用,因为我们所说的current fiber或者是workInProgress fiber都是视图的产物,是可以在"新"与"老"之间转换的。

为什么会出现Fiber架构呢?

相信在座的各位写React的同学出去面试,面试官总会问:”请问你知道React Fiber架构吗?请你说说Fiber架构吧“

为什么会出现?通过上面的React Fiber架构的讲解,我们可以get到几个点,那就是fiber针对每一个fiber节点都会有一套自己的独立的beginworkcompletework,并且能够在每一个具有副作用的节点上进行打标处理,而不是直接变更。而且生成的current fiberworkIProgress fiber可以相互转换,这里间接地可以称之为缓存吧。对比与以前的React应用来讲,以前的React应用是根据执行生命周期diffdom的更新一套流程同步走的,一套流程下来,不能中断,而且每一次的更新都是从根节点出发向下遍历的,我们可以设想一下处理庞大的结构的时候,那将是不可想象的性能开销,处理长时间任务耗时更长,更重要的是用户的交互,事件得不到及时响应,用户体验非常的差。

但是fiber这种结构,我们说的是一种时间分片的概念,通过时间分片把长任务,分成一个个独立的小单元去执行,返回。这样子就不会让js线程React应用独占,能有有空余去处理其他优先级较高的任务,任务得到了相应并且执行,当然了这种情况下页面就不会显得卡顿了。

所以总结来说就是React Fiber给我们提供了一种协调调度暂停中止调优的方式去更好的处理React应用浏览器的工作,保证了页面的性能与流畅度

总结

这一章讲述了整个的fiber架构与fiber树的创建与更新,那么这里从React应用的初始化挂载到React更新就形成了一部分的闭环完结,之后我们便是沿着流程走到了updateContainer更新这里

作者:zhang_a555原文地址:https://segmentfault.com/a/1190000043290666

%s 个评论

要回复文章请先登录注册