先看一下FiberNode在源码中的样子
FiberNode
 // packages/react-reconciler/src/ReactFiber.old.jsfunction 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:    ...  }}
       复制代码
 
 // packages/react-reconciler/src/ReactInternalTypes.jsexport 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,|};
       复制代码
 
dom 信息节点
tag: WorkTag
 我们看到这个`tag`为`WorkTag`类型,用来区分`React`组件的类型
       复制代码
 
 // packages/react-reconciler/src/ReactWorkTags.jsexport 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 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;
       复制代码
 
相关参考视频讲解:进入学习
上述代码,区分了组件的类型,在后期协调阶段beginWork、completeWork的流程里根据不同的类型组件去做不同的fiber节点的处理
key: null | string、type: any
 key为唯一值,type为与fiber关联的节点类型,都用于beginWork流程里面的reconcileChildren流程
       复制代码
 elementType
stateNode
 - stateNode 用于记录当前 fiber 所对应的真实 dom 节点或者当前虚拟组件的实例。- 便于实现Ref- 便于追踪Rdom
       复制代码
 fiber 链表树
fiber链表树里面有四个字段return、child、sibling、index
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代码中,我们通过某些手段去修改props、state等数据,数据修改完毕之后,但是同时引起了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 & Snapshotexport const LifecycleEffectMask = /*          */ 0b000000001110100100;
// Union of all host effectsexport 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 subtreeFlagsexport 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 invokingexport const MountLayoutDev = /*               */ 0b010000000000000000;export const MountPassiveDev = /*              */ 0b100000000000000000;
       复制代码
 
当然副作用不仅仅只是一个,所以React中在render阶段中采用的是深度遍历的策略去找出当前 fiber 树中所有的副作用,并维护一个副作用链表EffectList,与链表相关的字段还有firstEffect、nextEffect 和 lastEffect 我们来画一张图来简略示意一下。
解读一下就是,fristEffect指向第一个有副作用的fiber节点,lastEffect指向最后一个具有副作用的fiber节点,中间都是用nextEffect链接,这样组成了一个单向链表。render 阶段里面这一段处理就完了,在后面的commit阶段里面,React会根据EffectList里面fiber节点的副作用,会对应的处理相应的DOM,然后生成无副作用的虚拟节点,进行真实dom的创建。
优先级相关
当然React作为一个庞大的框架,肯定有自己的一套关于渲染的优先级机制,不然全都是一股脑按部就班的走,那肯定不行哒。
那么优先级我们就要关注一下lane与alternate,React中每个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.jsexport 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
memoizedProps: any
updateQueue: mixed
memoizedState: any
dependencies: Dependencies | null
Fiber 树的创建与更新的流程
上面一部分讲了React Fiber的基本架构,从真实dom信息、副作用、优先级等方面看了一下,为后面的render阶段的协调与调度以及commit阶段打下基础,那么接下来我们去探讨一下new FiberNode之后得到的什么样的rootFiber。我们在第二章节里面提到了整个的创建过程 # React 源码解析系列(二) -- 初始化组件的创建更新流程,那么这里深入探讨一下createFiber,在这个函数里面new FiberNode,创建了rootFiber,他也就是整个React应用的的根fiber。并且在createFiberRoot里面new FiberRootNode,创建了fiberRoot,它便是指向真实dom的根节点。所以在 # React 源码解析系列(二) -- 初始化组件的创建更新流程中我强调了root.current、uninitializedFiber.stateNode这两个东西,也就是这里说的rootFiber的stateNode字段指向了 fiberRoot,并且fiberRoot的current指向了rootFiber,具体的示例图如下:
所以这里就完成了fiber树根节点的创建了。
拿到了上面创建完成的rootFiber和fiberRoot之后那么我们接下来就是去根据我们的组件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树的形式是深度优先遍历,已beginwork和completework表示一个节点的创建过程,流程如下:
上面的图说明了,在初始化的时候我们的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节点都会有一套自己的独立的beginwork和completework,并且能够在每一个具有副作用的节点上进行打标处理,而不是直接变更。而且生成的current fiber与workIProgress fiber可以相互转换,这里间接地可以称之为缓存吧。对比与以前的React应用来讲,以前的React应用是根据执行生命周期、diff、dom的更新一套流程同步走的,一套流程下来,不能中断,而且每一次的更新都是从根节点出发向下遍历的,我们可以设想一下处理庞大的结构的时候,那将是不可想象的性能开销,处理长时间任务耗时更长,更重要的是用户的交互,事件得不到及时响应,用户体验非常的差。
但是fiber这种结构,我们说的是一种时间分片的概念,通过时间分片把长任务,分成一个个独立的小单元去执行,返回。这样子就不会让js线程被React应用独占,能有有空余去处理其他优先级较高的任务,任务得到了相应并且执行,当然了这种情况下页面就不会显得卡顿了。
所以总结来说就是React Fiber给我们提供了一种协调,调度,暂停,中止,调优的方式去更好的处理React应用与浏览器的工作,保证了页面的性能与流畅度
总结
这一章讲述了整个的 fiber 架构与 fiber 树的创建与更新,那么这里从 React 应用的初始化挂载到 React 更新就形成了一部分的闭环完结,之后我们便是沿着流程走到了updateContainer更新这里
评论