Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”. This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

React17 React17 React17 React17 React17 React17

After The release of Act 16, Fiber was introduced, and scheduling, coordination, diff algorithms, and rendering at the entire architecture level were all tied to Fiber. So in order to explain the rest of the story, it is necessary to have a clear understanding of Fiber. This chapter will introduce the following:

  • Why we need Fiber
  • Properties in the fiber node structure
  • How is the Fiber tree built and updated

Why we need Fiber

In his React Conf 2017 speech, Lin Clark described the emergence of Fiber in the form of comics. Now I will talk about the emergence of Fiber based on her speech and my own understanding.

Fiber before

Prior to React15 and fiber, React execution processes such as lifecycle execution, virtual DOM comparisons, and DOM tree updates were synchronized and continued until all workflows were completed.

React all state updates start from the root component. When the application component tree is large, once the state changes and the tree recurses, the js main thread has to stop other work. For example, there are 1000 components in the component tree that need to be updated, and it takes 1s for each component to be updated. In this case, the browser cannot do other things, and the user’s interaction events such as click input and page animation will not be responded to, and the experience will be very poor.

In this case, the call to the function stack looks like the following, which is very deep and does not return for a long time:

After the fiber

To solve this problem, React introduced a data structure called Fiber, which broke up large tasks that took a long time to update and render into many smaller pieces. After each small task is executed, other high-priority tasks (such as user clicks input events, animations, etc.) will be executed first, so that the main js thread will not be monopolized by React. Although the total execution time of tasks remains unchanged, the page can respond to high-priority tasks in a timely manner.

In fiber sharding mode, the main thread of the browser can be released periodically to ensure the frame rate of the rendering. The stack call of the function is as follows (trappings indicate the execution of the sharding task, peaks indicate the execution of other high-priority tasks) :React, via Fiber, provides an easy way to track, schedule, pause, and terminate work, ensuring performance and smoothness.

Fiber node structure

Fiber is a data structure that contains dom information, references to the fiber tree, and side effects when updating the fiber tree.

// packages/react-reconciler/src/ReactInternalTypes.js

export type Fiber = {|
  // As a static data structure, store node DOM information
  tag: WorkTag, // The type of the component depends on the react element type
  key: null | string.elementType: any.// Element type
  type: any.// Define the function or class associated with this fiber. For components, it points to the constructor; For DOM elements, it specifies the HTML tag
  stateNode: any.// Real DOM node
  
  // Fiber list tree correlation
  return: Fiber | null.Fiber / / parent
  child: Fiber | null.// The first sub-fiber
  sibling: Fiber | null.// Next brother Fiber
  index: number.// Subscript in the child fiber below the parent fiber
  
  ref:
    | null
    | (((handle: mixed) = > void) & {_stringRef:?string. }) | RefObject,// Unit of work, used to calculate the state and props rendering
  pendingProps: any.// Props to use for this rendering
  memoizedProps: any.// Props used for the last rendering
  updateQueue: mixed, // Queue for status updates, callback functions, DOM updates
  memoizedState: any.// State since last rendering
  dependencies: Dependencies | null.// Context, Events, etc
  
  mode: TypeOfMode,
  
  // Side effects are related
  flags: Flags, // Record the current fiber side effects (delete, update, replace, etc.) status when updated
  subtreeFlags: Flags, // Side effect status of the current subtree
  deletions: Array<Fiber> | null.// Subfiber to be removed
  nextEffect: Fiber | null.// The next side effect is fiber
  firstEffect: Fiber | null.// Point to the first fiber with side effects
  lastEffect: Fiber | null.// Point to the last fiber with side effects
  
  // Priority is related
  lanes: Lanes,
  childLanes: Lanes,
  
  alternate: Fiber | null.// point to the corresponding node in the workInProgress Fiber treeactualDuration? :number, actualStartTime? :number, selfBaseDuration? :number, treeBaseDuration? :number, _debugID? :number, _debugSource? : Source |null, _debugOwner? : Fiber |null, _debugIsCurrentlyTiming? :boolean, _debugNeedsRemount? :boolean, _debugHookTypes? :Array<HookType> | null|};Copy the code

Dom related attributes

Dom node information in Fiber focuses on tag, key, Type, and stateNode.

tag

The workType of the fiber tag attribute is workType, which is used to identify different react component types.

// packages/react-reconciler/src/ReactWorkTags.js

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

In react coordination, processes such as beginWork and completeWork will execute different functions to process fiber nodes according to different tag types.

The key and type

Key and type are used to determine whether fiber can be reused during react Diff.

Key is a unique value defined by the user. Type defines the functionality or classes associated with this Fiber. For components, it points to a function or to the class itself; For DOM elements, it specifies the HTML tag.

stateNode

The stateNode is used to record the actual DOM node or virtual component instance corresponding to the current fiber. This is done first for the purpose of Ref and second for the purpose of tracking the real DOM.

Linked list tree related properties

Let’s look at the return, child, and Sibling fields related to fiber tree construction:

  • Return: refers to parent fiber, or null if there is no parent fiber
  • Child: points to the first subfiber, or null if there are no subfibers
  • Sibling: points to the next sibling fiber, or null if there is no next sibling fiber

Each fiber node forms a fiber list tree using these fields:

Side effect related attributes

First of all, let’s understand the side effects of React. Here’s a common example: When we catch a cold, we take some medicine and get rid of it. But when we take the medicine, we find our body is allergic. In React, we modified the data of state, props, ref, etc. In addition to data changes, dom changes were also caused. This kind of work cannot be completed in the Render phase, which is called side effects.

flags

React uses flags to record the state that needs to be changed after each node diff, such as dom addition, replacement, and deletion. We can look at the Flags enumeration type in the source code:

For example, Deletion represents deleting the DOM when updating, Placement represents adding or replacing, and so on.

// packages/react-reconciler/src/ReactFiberFlags.js

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

Effect List

In the Render phase, React uses depth-first traversal to traverse the Fiber tree, filter out every fiber with side effects, and finally build an Effect list with only side effects. The fields associated with this list are firstEffect, nextEffect, and lastEffect:

FirstEffect points to the first fiber node with side effects, lastEffect points to the last fiber node with side effects, and all nodes in the middle are linked through nextEffect to form an Effect linked list.

During the COMMIT phase, React takes the data from the Effect List and changes the DOM for each Fiber node based on its FLAGS type.

other

Other properties to focus on are Lane and Alternate.

lane

Lane represents the priority of fiber tasks to be executed by React. With this field, the Render phase determines which tasks should be committed to the COMMIT phase first.

Let’s look at the enumeration of lane in the source code:

// packages/react-reconciler/src/ReactFiberLane.js

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

Like the enumeration values for Flags, Lanes are represented in a 31-bit binary number representing 31 tracks, with the smaller the number of digits representing the higher priority.

For example InputDiscreteHydrationLane, InputDiscreteLanes, InputContinuousHydrationLane user interaction caused by updated priority is higher, Requests such as DefaultLanes have a medium priority for causing updates, while OffscreenLane and IdleLanes have a lower priority.

The lower the priority, the more likely the task is to be interrupted during the RENDER phase and the later the commit is executed.

alternate

When the React state is updated, the fiber tree corresponding to the current page is called current Fiber, and the React builds a new fiber tree based on the new state, called workInProgress Fiber. Each Fiber node in current Fiber points to the corresponding Fiber node in workInProgress Fiber through the alternate field. Similarly, the alternate field of the Fiber node in workInProgress Fiber points to the corresponding Fiber node in Current Fiber.

Fiber tree construction and update

Here we combine the source code, look at the actual work process of fiber tree construction and update process.

Mount process

React mount starts with reactdom. render as the entry function, and a series of function calls follow: ReactDOM. Render — — > legacyRenderSubtreeIntoContainer – > legacyCreateRootFromDOMContainer – > createLegacyRoot – > ReactDOMBlockingRoot — > ReactDOMRoot — > createRootImpl — > createContainer — > createFiberRoot — > createHostRootFiber – > createFiber

In the createFiber function, call the FiberNode constructor to create the rootFiber, which is the rootFiber of the react application:

// packages/react-reconciler/src/ReactFiber.new.js

const createFiber = function(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) :Fiber {
  return new FiberNode(tag, pendingProps, key, mode);
};
Copy the code

In the createFiberRoot function, the FiberRootNode constructor is called to createFiberRoot, which points to the real root dom node.

// packages/react-reconciler/src/ReactFiberRoot.new.js

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

In createFiberRoot, the stateNode field of rootFiber points to fiberRoot, and the Current field of fiberRoot points to rootFiber. Thus a primitive fiber root node is created:

React creates a detailed DOM tree based on the JSX (for example, 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>
Copy the code

React Create and update fiber structures using depth-first traversal. Start with rootFiber, create child A1, find a1 has child node B1, and traversal b1 with child node C1. Create child nodes d1, d2, and d3 of C1 until d1, d2, and D3 have no child nodes. Then create c2.

The above procedure is performed when each node is createdbeginWorkThe process is executed until all descendants of the node have been created (updated)completeWorkThe process is illustrated as follows:

The update process

Update react will create a new workInProgress fiber based on the new JSX content, or use depth-first traversal to flag different side effects for the changed fiber. And form an Effect List linked List through fields such as firstEffect and nextEffect.

For example, the JSX structure above has the following update:

<div id="root">
  <div id="a1">
    <div id="b1">
      <div id="c1">
        <div id="d1"></div>
- 
      
-
</div> -
+
new content
</div> </div> </div> Copy the code

React creates a workInProgress fiber by calling createWorkInProgress.

// packages/react-reconciler/src/ReactFiber.new.js

export function createWorkInProgress(current: Fiber, pendingProps: any) :Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) { // distinguish between mount and 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; }}// Reset all side effects
  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;

  // Clone dependencies
  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;
}
Copy the code

The resulting workInProgress Fiber is shown below:

Then, as mentioned above, the corresponding alternate in Current Fiber and workInProgress Fiber will point to each other. Then, after the workInProgress Fiber is completely created, [Bug Mc-10868] – fiberRoot current field points to rootFiber in workInProgress Fiber instead of rootFiber in Current Fiber:

conclusion

This chapter explains the main reasons for fiber, the main properties in fiber nodes, and how fiber trees are built and updated.

React17 React17 React17 React17 React17 React17 React17 React17 React17 Examples include how the render process prioritizes tasks, how tasks are interrupted and resumed, how the diff process is executed, how pages are rendered in the Commit phase, and so on. Keep an eye on this column for updated chapters.