Learn about React Fiber architecture from first rendering

Key global variable

FiberRootNode related:

export type RootTag = 0 | 1 | 2;

export const LegacyRoot = 0;    
export const BlockingRoot = 1;
export const ConcurrentRoot = 2;
Copy the code

Mode FiberRootNode Attribute Tag possible value

export type TypeOfMode = number;

export const NoMode = 0b00000;  // Use this mod when creating RootFiber
export const StrictMode = 0b00001;
// TODO: Remove BlockingMode and ConcurrentMode by reading from the root
// tag instead
export const BlockingMode = 0b00010;
export const ConcurrentMode = 0b00100;
export const ProfileMode = 0b01000; // Use this mod when creating RootFiber in development mode
export const DebugTracingMode = 0b10000;
Copy the code

Update and reconcile

// The root we're working on
let workInProgressRoot: FiberRoot | null = null;
// The fiber we're working on
let workInProgress: Fiber | null = null;
// The lanes we're rendering
let workInProgressRootRenderLanes: Lanes = NoLanes;
Copy the code

Priority and Lane

// The larger the LanePriority, the higher the priority
export const SyncLanePriority: LanePriority = 15;
export const SyncBatchedLanePriority: LanePriority = 14;

const InputDiscreteHydrationLanePriority: LanePriority = 13;
export const InputDiscreteLanePriority: LanePriority = 12;

const InputContinuousHydrationLanePriority: LanePriority = 11;
export const InputContinuousLanePriority: LanePriority = 10;

const DefaultHydrationLanePriority: LanePriority = 9;
export const DefaultLanePriority: LanePriority = 8;

const TransitionHydrationPriority: LanePriority = 7;
export const TransitionPriority: LanePriority = 6;

const RetryLanePriority: LanePriority = 5;

const SelectiveHydrationLanePriority: LanePriority = 4;

const IdleHydrationLanePriority: LanePriority = 3;
const IdleLanePriority: LanePriority = 2;

const OffscreenLanePriority: LanePriority = 1;

export const NoLanePriority: LanePriority = 0;

// Corresponding to LanePriority, the smaller Lane is, the higher the priority is. The binary display of priority can display the current permissions more directly, and it is more convenient to merge and extract permissions
const TotalLanes = 31;

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;

export const NoTimestamp = -1;

let currentUpdateLanePriority: LanePriority = NoLanePriority;
Copy the code

State update mode

export const UpdateState = 0;
export const ReplaceState = 1;
export const ForceUpdate = 2;
export const CaptureUpdate = 3;
Copy the code

Possible value of the FiberNode Tag attribute

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;
Copy the code

FiberNode

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 tree link list
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  // Component property data
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;  // Update the queue, which holds the status of the changes to be made
  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;  // FiberNode, one of the double buffers
}
Copy the code

FiberRootNode

function FiberRootNode(containerInfo, tag, hydrate) {
  this.tag = tag;
  this.containerInfo = containerInfo; // Render the React element inside the DOM
  this.pendingChildren = null;
  this.current = null;  // (HostRoot)FiberNode, pointing to the Root of the currently completed Fiber Tree
  this.pingCache = null;
  this.finishedWork = null; / / (HostRoot) FiberNode | null, points to the current has been completed preparations for the real render Tree Root Fiber
  this.timeoutHandle = noTimeout;
  this.context = null;
  this.pendingContext = null;
  this.hydrate = hydrate;
  this.callbackNode = null;
  this.callbackPriority = NoLanePriority;
  this.eventTimes = createLaneMap(NoLanes); // Update time array, 31 bit array
  this.expirationTimes = createLaneMap(NoTimestamp);    // Expiration time array, 31 bits
  this.pendingLanes = NoLanes;
  this.suspendedLanes = NoLanes;
  this.pingedLanes = NoLanes;
  this.expiredLanes = NoLanes;
  this.mutableReadLanes = NoLanes;
  this.finishedLanes = NoLanes;
  this.entangledLanes = NoLanes;
  this.entanglements = createLaneMap(NoLanes);  // An entangled array, a 31-bit array such as MutableSource
}
Copy the code

Mainline method calls from assembly data to task scheduling

legacyRenderSubtreeIntoContainer => createRootFiber= > FiberRootNode= > initialUpdateQueue= > updateContainer= > createUpdate= > scheduleUpdateOnFiber= > renderRootSync= > workLoopSync= > performUnitOfWork= > 
Copy the code

Create ReactRoot, FiberRoot, and (HostRoot)FiberNode to establish their relationship with DOMContainer

legacyRenderSubtreeIntoContainer

  • Create initial FiberRoot from container: root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate = false)
  • fiberRoot = root._internalRoot;
  • // Initial mount should not be batched
  • UpdateContainer starts preparing for scheduling

CreateFiberRoot createFiberRoot

Create root: FiberRootNode:

  • willroot.currentThe assignment forcreateHostRootFiber(tag = NoMode)Created by theFiberNodeRoot.current. Mode = NoMode, which will be ProfileMode in the development environment.
  • willroot.current.stateNodeThe assignment forroot;
  • If root is returned, FiberRootNode is successfully created.

MarkContainerAsRoot in the createRootImpl method

Add the Container to the FiberNode created above

MarkContainerAsRoot (root. Current, the container) add conrainer properties [internalContainerInstanceKey] = root. The current

RequestEventTime gets the timestamp of the currently executing event, global variable

function requestEventTime() {
  if((executionContext & (RenderContext | CommitContext)) ! == NoContext) {// We're inside React, so it's fine to read the actual time.
    return now();
  } // We're not inside React, so we may be in the middle of a browser event.


  if(currentEventTime ! == NoTimestamp) {// Use the same start time for all updates until we enter React again.
    return currentEventTime;
  } // This is the first update since React yielded. Compute a new start time.


  currentEventTime = now();
  return currentEventTime;
}
Copy the code

Initialize (HostRoot)FiberNode UpdateQueue

UpdateContainer starts scheduling related work

  • Get the current execution time;
  • RequestUpdateLane calculates the priority according to the FiberNode mod value;
  • CreateUpdate (eventTime, Lane), which initializes an update based on the input, returns as follows:
return update = {
    eventTime: eventTime,   // Update event time, current timestamp
    lane: lane, // The lane priority is related, 1
    tag: UpdateState,   // Common update mode
    payload: null.callback: null.next: null
  };
Copy the code
  • Update the content to the element passed in by render, and then generate the fiber Tree from the element as follows:
update.payload = {
    element: element
};
Copy the code
  • EnqueueUpdate (container.current, update), where the next update to the first update points to the current update (update.next = update;) = > container. Current. UpdateQueue. Shared. Pending points to update
updateQueue = {
    baseState: element, // The first update is assigned to the element for which the task is currently being performed
    firstBaseUpdate: null.// The first 'Update' in the queue
    lastBaseUpdate: null.// The last 'Update' in the queue
    shared: {
      pending: null // Prepare the update task to execute
    },
    effects: null   / / side effects
}
Copy the code
  • scheduleUpdateOnFiber(container.current, lane, eventTime);
  • MarkUpdateLaneFromFiberToRoot (fiber, lane), to the fiber properties lanes assignment
  • markRootUpdated(root, updateLane, eventTime)
root.pendingLanes |= updateLane;    // Lanes to be implemented
const higherPriorityLanes = updateLane - 1; // Calculate lanes with a higher priority than the current lane

 // Cancel updates of equal or lower priority.
 root.suspendedLanes &= higherPriorityLanes; / / 0
 root.pingedLanes &= higherPriorityLanes;  / / 0

 const index = laneToIndex(updateLane); // Get index, 0 based on updateLane
 eventTimes[index] = eventTime;   // Update the time when the update is triggered
Copy the code
  • PerformSyncWorkOnRoot Starts the synchronization task at root, processing updates
lanes = getNextLanes(root, NoLanes);;  // Obtain lanes for the next task based on the updateQueue mounted by root fiber
exitStatus = renderRootSync(root, lanes = 1); // synchronize render root
Copy the code
  • renderRootSync

WorkInProgressRoot = root; workInProgress = createWorkInProgress(root.current, null); To the global variable workInProgress assignment for a copy of the current fiber node workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes; workInProgressRootExitStatus = RootIncomplete; workInProgressRootFatalError = null; workInProgressRootSkippedLanes = NoLanes; workInProgressRootUpdatedLanes = NoLanes; workInProgressRootPingedLanes = NoLanes; * /
prepareFreshStack(root, lanes); // Prepare new task stack related
/* performUnitOfWork (); /* beginWork (); /* performUnitOfWork () A sibling attribute is created for each node to point to the next sibling node and the completeUnitOfWork method is called when traversing to the deepest node of a branch (there are no children) to determine if there is Sibling (the next sibling) returns to performUnitOfWork if there is no sibling look for return (parent), If the parent has sibling continue to return to performUnitOfWork and finally return null, it means that all traversals have been completed to implement depth-first traversal to create the entire Fiber Tree while (workInProgress! == null) { performUnitOfWork(workInProgress); } * /
workLoopSync();
Copy the code
// Global variables
export let current: Fiber | null = null;    // Fiber is currently in operation
export let isRendering: boolean = false;    // Whether render is being performed

const valueStack: Array<any> = [];  // 

let fiberStack: Array<Fiber | null>; / / FiberNode stack
Copy the code

performUnitOfWork(unitOfWork: FiberNode)

var current = unitOfWork.alternate;
/* current = fiber; isRendering = false; * /
setCurrentFiber(unitOfWork);
next = beginWork(current, unitOfWork, subtreeRenderLanes);  // Get the next update queue.next and clone the current. UpdateQueue to workInProgress, which is the Fiber tree where the operation is actually performed, without affecting current

// The main thread makes the method call
beginWork= > updateHostRoot= > processUpdateQueue= > reconcileChildren= > reconcileChildFibers= > reconcileSingleElement= > createFiberFromElement= > completeUnitWork= > completeWork= > createInstance= > createElement= > finalizeInitialChildren
Copy the code
  • updateHostRoot

    Depth-first traversal and process according to Update Ue update the corresponding fiber

    • ProcessUpdateQueue processes the update queue, Include state (currently only element), reassign the first update, reassign the last update, and change the pointer to update. Next Loop until the pendingQueue is empty.

    • ReconcileChildren Coordinates child nodes

      The implementation of the Fiber Reconciler is divided into two stages.

      • In the first stage, Fiber tree is generated through element and node information to be updated is obtained. This step is a gradual process that can be interrupted.
      • In phase 2, nodes that need to be updated are updated in batches at a time. This process cannot be interrupted.

      Depth-first processing

    • ReconcileSingleElement reconciles individual child node elements

      • If the key is the same and the corresponding type meets time Fragment, Protal, or other conditions, reuse the key and delete the remaining nodes. If the key type does not match, the current node is deleted.

      • If the key and type do not match, delete the current fiber. In addition, create fiber separately from frament and other cases.

    • CreateFiberFromElement Creates a new Fiber tree based on the Element

  • completeUnitOfWork

Determine if there is a sibling (next sibling) and return it to performUnitOfWork

If there is no sibling, find the return (parent), if the parent has sibling, continue to return to performUnitOfWork, thus implementing depth-first traversal to create the entire Fiber tree.

  • CompleteWork creates the real DOM and adds properties by calling createInstance => createElement => finalizeInitialChildren,

  • Finally, commitRoot is added to the container, div#root, to complete the initial rendering.

conclusion

  • Create the initial FiberRootNode
  • Create the initial rootFiber and initialize updateQueue
  • Create an update based on Element and assign it to updateQueue
  • Copy FiberRoot.current to global workInProgress according to updateQueue and tune fiber Tree to generate the latest Fiber Tree
  • Create the latest DOM tree according to the workInProgress loop
  • Add the DOM tree to the Container
// overall mainline method call
legacyRenderSubtreeIntoContainer => createRootFiber= > FiberRootNode= > initialUpdateQueue= > updateContainer= > createUpdate= > scheduleUpdateOnFiber= > renderRootSync= > workLoopSync= > performUnitOfWork= > beginWork= > updateHostRoot= > processUpdateQueue= > reconcileChildren= > reconcileChildFibers= > reconcileSingleElement= > createFiberFromElement= > completeUnitWork= > completeWork= > createInstance= > createElement= > finalizeInitialChildren= > commitRoot
Copy the code