Fiber is currently the core of react, which contains a lot of computer knowledge. If you read the react source code, you’ll feel powerless.

With each release, the core functions always move around. From 15 to 16 to 16.8, 16.9 to now 16.10, including the now revamped VUe3.0, it’s really uncomfortable. But the pace of learning still can’t stop. The React series relies on the 16.9 source code for analysis.

Let’s get back to the point. This chapter and the next several articles are all about learning and understanding fiber, including construction, scheduling, updating, etc. If there is an error, please submit an issue under git blog project.

What is the fiber

Fiber is a granular concept of threads. This means that a thread can contain multiple fibers. Synchronous computing can be decoupled and asynchronous, allowing the browser main thread to be regulated.

By fragmenting the update process, control is handed back to the React task coordination module when an update is completed, and the update operation is performed by scheduling the task priorities of the system.

Create FiberRoot

We’ll start this article with the creation of FiberRoot. JSX is converted primarily at compile time through the react. createElement function, which we described in the JSX transformation. Then the converted code in ReactDOM. Render in execution, we look at the first ReactDOM. Render the execution of the function, defined in the file packages/react – dom/SRC/client/ReactDOM js

const ReactDOM: Object = {
 // ...
  hydrate(element: React$Node, container: DOMContainer, callback:?Function) {
    invariant(
      isValidContainer(container),
      'Target container is not a DOM element.',);if (__DEV__) {
      // ...
    }
    // TODO: throw or warn if we couldn't hydrate?
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      true,
      callback,
    );
  },

  render(
    element: React$Element<any>,
    container: DOMContainer,
    callback:?Function,
  ) {
    invariant(
      isValidContainer(container),
      'Target container is not a DOM element.',);if (__DEV__) {
      // ...
    }
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  }
Copy the code

We already see the render function in the above part of the code, but the hydrate function is executed before the render function is executed. This code is new to React16 and is mainly used to reconcile server render with local DOM. Finally, the fiberRoot process is differentiated. The specific process will be analyzed later.

Let’s see the render function, the invariant verify whether effective DOM element and then execute the legacyRenderSubtreeIntoContainer one of the main method

legacyRenderSubtreeIntoContainer


The main role is legacyRenderSubtreeIntoContainer method

  • Call legacyCreateRootFromDOMContainer logo _reactRootContainer called the root element, here forceHydrate is a sign to distinguish whether the server below
  • Call ReactSyncRoot to create fiberRoot
  • Call unbatchedUpdates. The first render is done without batch updates. Finally, call updateContainer to update the child node to generate the complete Fiber tree
// Render the Dom Tree to the mounted container node
function legacyRenderSubtreeIntoContainer(parentComponent: ? React$Component
       
        , children: ReactNodeList, container: DOMContainer, forceHydrate: boolean, callback: ? Function,
       ,>) {
  if (__DEV__) {
    // ...
  }

  // Check whether the container has _reactRootContainer. Normally, the container does not have _reactRootContainer when it is rendered for the first time
  let root: _ReactSyncRoot = (container._reactRootContainer: any);
  let fiberRoot;
  if(! root) {// Initialize the root object for the first rendering
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    // Combine the ReactSyncRoot function this._internalRoot = root;
    // fiberRoot returns the result for the createContainer function
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // First render does not use batch update.
    unbatchedUpdates((a)= > {
      // Update child nodes in batches
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // Update
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}
Copy the code

Through the above general process, to explain in detail the three steps of the execution process

legacyCreateRootFromDOMContainer


LegacyCreateRootFromDOMContainer, this method is used

  • Determine if it is shouldHydrate on the server rendering flag
  • Removes all child elements removeChild
  • Returns an instance of ReactSyncRoot

function legacyCreateRootFromDOMContainer(container: DOMContainer, forceHydrate: boolean,) :_ReactSyncRoot {

  // Determine if existing DOM nodes will be harmonized (multiplexed) to improve performance, and then merged with newly rendered nodes via the hydrate flag being passed
  const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
  if(! shouldHydrate) {let warned = false;
    let rootSibling;
    // Delete all child nodes under the Container
    while ((rootSibling = container.lastChild)) {
      if (__DEV__) {
        // ...} container.removeChild(rootSibling); }}if (__DEV__) {
    // ...
  }

  // Root is updated synchronously.
  return new ReactSyncRoot(container, LegacyRoot, shouldHydrate);
}

Copy the code

ReactSyncRoot


ReactSyncRoot, the main function of this method is

  • Call createContainer
  • A fiberRoot is created for assignment
function ReactSyncRoot(container: DOMContainer, tag: RootTag, hydrate: boolean,) {
  // Assign the value returned by createContainer to the _internalRoot of the instance
  const root = createContainer(container, tag, hydrate);
  this._internalRoot = root;
}
Copy the code

createContainer


CreateContainer This method is derived from the React-Reconciler/inline-dom

import {
  computeUniqueAsyncExpiration,
  findHostInstanceWithNoPortals,
  updateContainerAtExpirationTime,
  flushRoot,
  createContainer,
  updateContainer,
  batchedEventUpdates,
  batchedUpdates,
  unbatchedUpdates,
  discreteUpdates,
  flushDiscreteUpdates,
  flushSync,
  flushControlled,
  injectIntoDevTools,
  getPublicRootInstance,
  findHostInstance,
  findHostInstanceWithWarning,
  flushPassiveEffects,
  IsThisRendererActing,
} from 'react-reconciler/inline.dom';
Copy the code

You can see a lot of related methods defined in this file that we introduced, so let’s go into this file

export * from './src/ReactFiberReconciler';
Copy the code

As you can see from the name, this is fiber’s build file. Go to this file and view createContainer

export function createContainer(containerInfo: Container, tag: RootTag, hydrate: boolean,) :OpaqueRoot {
  return createFiberRoot(containerInfo, tag, hydrate);
}
Copy the code

The main function of this method is

  • Return the value of the createFiberRoot method

createFiberRoot


The createFiberRoot method is derived from the file./ReactFiberRoot

  • Call createHostRootFiber to return uninitializedFiber
  • Assign uninitializedFibe to current on the root object
  • The root assigned to uninitializedFiber stateNode
export function createFiberRoot(containerInfo: any, tag: RootTag, hydrate: boolean,) :FiberRoot {
   // The root node is fiber
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
  // loop structure to reference each other
  // The whole fiber tree is connected in series using this data structure
  const uninitializedFiber = createHostRootFiber(tag);
  / / current is uninitializedFiber
  // Each ReactElment node corresponds to a Fiber object, which creates a tree structure
  // root.current is the vertex of the current fiber tree
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  return root;
}
Copy the code

FiberRootNode and createHostRootFiber are executed to create the entire structure. What is the relationship between them

  • Root is an instance of ReactRoot,
  • Root. _internalRoot is a fiberRoot instance,
  • Root. _internalroot. current is an instance of Fiber,
  • root._internalRoot.current.stateNode = root._internalRoot

FiberRootNode


New FiberRootNode This method creates a FiberRootNode, which is the root node of fiber

function FiberRootNode(containerInfo, tag, hydrate) {
  // Mark different component types
  this.tag = tag;
  // The corresponding root node is also the corresponding fiber object
  this.current = null;
  // The second argument received in the dom root node render
  // ReactDOM.render(element, document.getElementById('root'));
  this.containerInfo = containerInfo;
  // Used in persistent updates
  this.pendingChildren = null;
  this.pingCache = null;

  this.finishedExpirationTime = NoWork;
  // Record the priority of the update task. Only the tasks corresponding to this value are processed during the COMMIT phase
  this.finishedWork = null;
  // Return the content set by setTimeout when a task is suspended, which is used to clear the timeout that has not been triggered the next time a new task is suspended
  this.timeoutHandle = noTimeout;
  this.context = null;
  this.pendingContext = null;
  // Whether to use the blending flag
  this.hydrate = hydrate;
  this.firstBatch = null;
  this.callbackNode = null;
  this.callbackExpirationTime = NoWork;
  this.firstPendingTime = NoWork;
  this.lastPendingTime = NoWork;
  this.pingTime = NoWork;

  if (enableSchedulerTracing) {
    this.interactionThreadID = unstable_getThreadID();
    this.memoizedInteractions = new Set(a);this.pendingInteractionMap = new Map();
  }
}

Copy the code

CreateHostRootFiber is then executed

createHostRootFiber


The createHostRootFiber method comes from./ReactFiber

  • Use RootTag to compare and differentiate the mode of creating fiber
  • Execute createFiber to get the return value
export function createHostRootFiber(tag: RootTag) :Fiber {
  let mode;
  if (tag === ConcurrentRoot) {
    mode = ConcurrentMode | BatchedMode | StrictMode;
  } else if (tag === BatchedRoot) {
    mode = BatchedMode | StrictMode;
  } else {
    mode = NoMode;
  }

  if (enableProfilerTimer && isDevToolsPresent) {
    mode |= ProfileMode;
  }

  return createFiber(HostRoot, null.null, mode);
}

Copy the code

There are a few variables in the comparison process above that I find interesting. If other students are not interested, you can skip the following part

import {ConcurrentRoot, BatchedRoot} from 'shared/ReactRootTags';

export type RootTag = 0 | 1 | 2;

export const LegacyRoot = 0;
export const BatchedRoot = 1;
export const ConcurrentRoot = 2;

Copy the code
import {
  NoMode,
  ConcurrentMode,
  ProfileMode,
  StrictMode,
  BatchedMode,
} from './ReactTypeOfMode';


export type TypeOfMode = number;

export const NoMode = 0b0000;
export const StrictMode = 0b0001;
// Delete BatchedMode and ConcurrentMode from root
export const BatchedMode = 0b0010;
export const ConcurrentMode = 0b0100;
export const ProfileMode = 0b1000;

Copy the code

The expiriation-time mode is determined by the above binary calculation, about which we will introduce in a subsequent article.

Let’s take a look at createFiber

createFiber


CreateFiber This method returns a FiberNode instance for FiberNode initialization

const createFiber = function(tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,) :Fiber {
  // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
  return new FiberNode(tag, pendingProps, key, mode);
};
Copy the code

FiberNode


FiberNode

function FiberNode(tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,) {
  // Instance
  // Distinguish between fiber types and mark different component types
  this.tag = tag;
  // ReactElement key
  this.key = key;
  // Call the first argument to createElement
  this.elementType = null;
  'function' or 'class' will return when the async component is resolved
  this.type = null;
  // The instance of the corresponding node
  this.stateNode = null;

  // Fiber single-linked list tree structure.
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  // New props stored in pendingProps after setState
  this.pendingProps = pendingProps;
  // memoizedProps after setState memoizedProps
  this.memoizedProps = null;
  // Updates created by setState or forceUpdate are stored in this queue
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  // Update mode of the node
  this.mode = mode;

  // Effects
  // a tool to mark which final DOM nodes are to be updated
  // Flags whether to execute the contents of the component lifecycle
  this.effectTag = NoEffect;
  this.nextEffect = null;

  this.firstEffect = null;
  this.lastEffect = null;
  // Expiration time of task priority scheduling
  this.expirationTime = NoWork;
  this.childExpirationTime = NoWork;

  this.alternate = null;

  // ...
}

Copy the code

The above fiberRoot initialization process completes and is created at the same time

unbatchedUpdates((a)= > {
  updateContainer(children, fiberRoot, parentComponent, callback);
});
Copy the code

Now let’s look at how do we update the child node

unbatchedUpdates

After creating fiberRoot, execute updateContainer in unbatchedUpdates to update the contents of the container. So what did unbatchedUpdates do before executing the update? Next up, unbatchedUpdates

UnbatchedUpdates not to carry on the batch update, defined in the file packages/react – the reconciler/SRC/ReactFiberWorkLoop. Js

export function unbatchedUpdates<A.R> (fn: (a: A) = >R.a: A) :R {
  // type ExecutionContext = number;
  // const NoContext = /* */ 0b000000;
  // const BatchedContext = /* */ 0b000001;
  // const EventContext = /* */ 0b000010;
  // const DiscreteEventContext = /* */ 0b000100;
  // const LegacyUnbatchedContext = /* */ 0b001000;
  // const RenderContext = /* */ 0b010000;
  // const CommitContext = /* */ 0b100000;
  // let executionContext: ExecutionContext = NoContext;

  const prevExecutionContext = executionContext;
  executionContext &= ~BatchedContext;
  executionContext |= LegacyUnbatchedContext;
  try {
    return fn(a);
  } finally {
    executionContext = prevExecutionContext;
    if (executionContext === NoContext) {
      Flushes the instant callback scheduled during this batchflushSyncCallbackQueue(); }}}Copy the code

The concept of an operator

  • & Set each of the two digits to 1 if both digits are 1
  • | if one of the two set each for 1 to 1
  • ~ reverses the operand’s bit, that is, 0 becomes 1,1 becomes 0

The above expression is equivalent to the following, initialized with the value

executionContext = executionContext & (~BatchedContext)  // 0 & (~1) is 0
executionContext = executionContext | LegacyUnbatchedContext / / | 0 8 of 8
Copy the code

ExecutionContext is the result of a combination of these contexts:

  • Add the current context render: — an optional executionContext | = RenderContext
  • ExecutionContext &= RenderContext == NoContext
  • Remove render: executionContext &= ~RenderContext

So the execution flow of this method is

  • Assign the current execution context to prevExecutionContext which defaults to 0
  • The current context is set to BatchedContext default is 0
  • Set the current context to LegacyUnbatchedContext default is 8
  • FlushSyncCallbackQueue is executed after the callback in try
  • Finally, the execution result is returned

The callback in the initial update phase try is actually updateContainer, so let’s look at updateContainer

updateContainer


UpdateContainer defined in/packages/react – the reconciler/SRC/ReactFiberReconciler. Js

export function updateContainer(element: ReactNodeList, container: OpaqueRoot, parentComponent: ? React$Component
       
        , callback: ? Function,
       ,>) :ExpirationTime {
  // As mentioned above, this corresponds to a fiber object
  const current = container.current;
  // Get the current task time
  // Expiration time is calculated by adding the current time (start time)
  const currentTime = requestCurrentTime();
  if (__DEV__) {
   // ...
  }

  // Batch process configuration SuspenseConfig defaults to null
  // export type SuspenseConfig = {
  // timeoutMs: number,
  //  busyDelayMs?: number,
  //  busyMinDurationMs?: number,
  // };
  const suspenseConfig = requestCurrentSuspenseConfig();
  // expirationTime is used to calculate the expirationTime of a node
  // If two updates are scheduled in the same event, their start times should be treated as simultaneous
  // Even though the actual clock time has been advanced between the first and second calls
  // Expiration time determines how updates are batched. All updates with the same priority occurring in the same event receive the same expiration time
  const expirationTime = computeExpirationForFiber(
    currentTime,
    current,
    suspenseConfig,
  );
  return updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    suspenseConfig,
    callback,
  );
}

Copy the code

The logic of the main two methods above computeExpirationForFiber and updateContainerAtExpirationTime

computeExpirationForFiber


ComputeExpirationForFiber compute node expiration time

export function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber, suspenseConfig: null | SuspenseConfig,) :ExpirationTime {
  // The current mode of the Fiber object
  const mode = fiber.mode;

  NoMode 0b0000 --> 0 * BatchedMode 0b0010 --> 2 * ConcurrentMode 0b0100 --> 4 * /

  /** * From./ReactFiberWorkLoop * ExecutionContext of the current scheduling system default NoContext 0b000000 --> 0 * RenderContext 0b010000 --> 16 */
  
  / * * * from the/ReactFiberExpirationTime processing pattern * * scheduling system Sync default MAX_SIGNED_31_BIT_INT - > 1073741823 (maximum 31 integer. Maximum integer size in V8 for 32-bit systems, math.pow (2, 30) - 1) * Batched Sync - 1 * LOW_PRIORITY_EXPIRATION 5000 * LOW_PRIORITY_BATCH_SIZE 250 * HIGH_PRIORITY_EXPIRATION __DEV__ ? 500 : 150 * HIGH_PRIORITY_BATCH_SIZE 100 */

  / * * *. / SchedulerWithReactIntegration * besides NoPriority, these corresponds to the scheduler priority * using ascending Numbers, begin from 90, To avoid a priority conflict with the scheduler * ImmediatePriority 99 * UserBlockingPriority 98 * NormalPriority 96 * IdlePriority 95 * NoPriority 90 */

  // (mode & 2) === 0 
  if ((mode & BatchedMode) === NoMode) {
    // return 1073741823
    return Sync;
  }
  const priorityLevel = getCurrentPriorityLevel();
  // (mode & 4) === 0
  if ((mode & ConcurrentMode) === NoMode) {
    // priorityLevel === 99 ? 1073741823: (1073741823-1)
    return priorityLevel === ImmediatePriority ? Sync : Batched;
  }
  // (0 & 16-> 0b010000) ! == 0 (0b000000)
  if((executionContext & RenderContext) ! == NoContext) {// Use the time we have rendered, default is nowork (0)
    return renderExpirationTime;
  }


  // expirationTime indicates the priority of this fiber. The larger the value is, the higher the priority is. Sync has the highest priority
  let expirationTime;
  if(suspenseConfig ! = =null) {
    // Calculate the expiration time based on the pause timeout
    expirationTime = computeSuspenseExpiration(
      currentTime,
      suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION,
    );
  } else {
    // Calculate expirationTime according to the scheduler priority
    switch (priorityLevel) {
      case ImmediatePriority:
        expirationTime = Sync; 
        break;
      case UserBlockingPriority:
        expirationTime = computeInteractiveExpiration(currentTime); 
        break;
      case NormalPriority:
      case LowPriority:
        expirationTime = computeAsyncExpiration(currentTime);
        break;
      case IdlePriority:
        expirationTime = Never;
        break;
      default:
        invariant(false.'Expected a valid priority level'); }}// If you are rendering a tree, do not update the operation on an expiration date that has already been rendered.
  // If updates are moved to separate batches on different root nodes
  if(workInProgressRoot ! = =null && expirationTime === renderExpirationTime) {
    expirationTime -= 1;
  }

  return expirationTime;
}

Copy the code

Comments can be seen from the top to code computeExpirationForFiber mainly depending on the task priority schedule to perform calculations expiration time expirationTime, then under the different task priority is how to calculate

There are mainly the following four kinds

  • The calculation of computeSuspenseExpiration suspended task
  • Sync Indicates the calculated value of a synchronization task
  • Update the calculation of computeInteractiveExpiration interaction
  • ComputeAsyncExpiration Calculation of asynchronous tasks

The above methods in addition to Sync task priority up to 99, other need to compute, calls the. / ReactFiberExpirationTime file computeExpirationBucket method


/*
* computeSuspenseExpiration (currentTime, timeoutMs, LOW_PRIORITY_BATCH_SIZE) {}
* timeoutMs --> suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION
* LOW_PRIORITY_BATCH_SIZE --> 200
*/

/*
* computeInteractiveExpiration (currentTime, HIGH_PRIORITY_EXPIRATION, HIGH_PRIORITY_BATCH_SIZE) {}
* HIGH_PRIORITY_EXPIRATION --> __DEV__ ? 500 : 150
* HIGH_PRIORITY_BATCH_SIZE --> 100
*/

/*
* computeAsyncExpiration (currentTime, LOW_PRIORITY_EXPIRATION, LOW_PRIORITY_BATCH_SIZE) {}
* LOW_PRIORITY_EXPIRATION --> 5000
* LOW_PRIORITY_BATCH_SIZE --> 250
*/

// MAGIC_NUMBER_OFFSET = Batched-1 --> (Sync - 1)-1 --> 1073741821;
// UNIT_SIZE = 10
function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs,) :ExpirationTime {
  return (
    MAGIC_NUMBER_OFFSET -
    ceiling(
      MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE,
      bucketSizeMs / UNIT_SIZE,
    )
  );
}

function ceiling(num: number, precision: number) :number {
  return (((num / precision) | 0) + 1) * precision;
} 

Copy the code

According to the above formula, we calculate the expirationTime by different task priorities

ComputeAsyncExpiration expirationTime for

1073741821-ceiling(1073741821-currentTime + 500.25)
1073741821- ((((1073741821 - currentTime + 500) / 25) | 0) + 1) * 25Let's take the last four bits and see what the pattern is and change currentTime to zero996- 1047.The number of1821- ((((1821 - 996 + 500) / 25) | 0) + 1) * 25  / / 1821-1350
1821- ((((1821 - 1021+ 500) / 25) | 0) + 1) * 25  / / 1821-1325
1821- ((((1821 - 1022+ 500) / 25) | 0) + 1) * 25  / / 1821-1300
1821- ((((1821 - 1047+ 500) / 25) | 0) + 1) * 25  / / 1821-1275
Copy the code

It can be concluded that the same result is obtained when executed within a span of 996-1021 digits, so the expiration interval for asynchronous updates is 25ms

Similarly computeInteractiveExpiration expirationTime for

1073741821-ceiling(1073741821-currentTime+15.10)
Copy the code

It can be concluded that the expiration interval for interactive updates is 10ms

In computeSuspenseExpiration suspenseConfig is used to solve the problem of asynchronous I/o, it calculates the priority depends on suspenseConfig. TimeoutMs time, If suspenseconfig. timeoutMs does not have a value then the default is LOW_PRIORITY_EXPIRATION (5000), LOW_PRIORITY_BATCH_SIZE (250), which is replaced by

1073741821-ceiling(1073741821-currentTime+500.25)
Copy the code

It can be concluded that the interval is 25ms, but since the value of 500 is not fixed, its priority is not fixed

From the conclusion of the above calculation

  • Sync task priority highest > computeInteractiveExpiration user interaction update task > computeAsyncExpiration asynchronous task computing
  • ComputeSuspenseExpiration situation because no fixed task priority are not fixed

React asynchronous updates have a expirationTime interval of 25ms, so that two or more similar updates (within 25ms) get the same expirationTime. The purpose is to automatically merge these two updates into one, so as to achieve the purpose of automatic batch update. In this way, constant setState() updates during development will improve performance within 25ms. We will also look at how setState and BatchUpdate work later.

updateContainerAtExpirationTime


UpdateContainerAtExpirationTime defined in the file packages/react – the reconciler/SRC/ReactFiberReconciler. Js

export function updateContainerAtExpirationTime(element: ReactNodeList, container: OpaqueRoot, parentComponent: ? React$Component
       
        , expirationTime: ExpirationTime, suspenseConfig: null | SuspenseConfig, callback: ? Function,
       ,>) {
  // TODO: If this is a nested container, this won't be the root.
  const current = container.current;

  if (__DEV__) {
    // ..
  }

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  return scheduleRootUpdate(
    current,
    element,
    expirationTime,
    suspenseConfig,
    callback,
  );
}
Copy the code

See the node that fetched the container from above, and then enter the scheduleRootUpdate to update the Container

scheduleRootUpdate


scheduleRootUpdate

function scheduleRootUpdate(current: Fiber, element: ReactNodeList, expirationTime: ExpirationTime, suspenseConfig: null | SuspenseConfig, callback: ? Function,) {
  if (__DEV__) {
    // ...
  }

  Update objects are created to record the changed state of the component, just as with synchronous updates
  const update = createUpdate(expirationTime, suspenseConfig);
  // Set the update property for the first render
  update.payload = {element};

  callback = callback === undefined ? null : callback;
  if(callback ! = =null) {
    // ...
    update.callback = callback;
  }

  if (revertPassiveEffectsChange) {
    flushPassiveEffects();
  }
  // enqueueUpdate adds the update object to the updateQueue of the fiber object, which is a one-way linked list structure
  // Whenever an update is triggered by setState or other means, an update is inserted into the Update ue on Fiber
  // So that the update can be merged with the update
  enqueueUpdate(current, update);
  // The task scheduling process starts. Task scheduling needs to be controlled based on priorities
  // If there are different task priorities at the same time, the task with higher priority needs to be executed first
  scheduleWork(current, expirationTime);

  return expirationTime;
}
Copy the code

scheduleWork


ScheduleWork asynchronous scheduling process, defined in the file/packages/react – the reconciler/SRC/ReactFiberWorkLoop. Js

export function scheduleUpdateOnFiber(fiber: Fiber, expirationTime: ExpirationTime,) {

  // Determine if there is an infinite loop of updates, if there are more than 50 update levels
  // It stops scheduling with an error
  // So we can't call setState unconditionally in render, causing an endless loop
  checkForNestedUpdates();
  // ...
  // Find rootFiber and iterate through the expirationTime of the updated child
  const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
  if (root === null) {
    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
    return;
  }
  // NoWork indicates 0 if no update operation is performed
  root.pingTime = NoWork;
  // Determine whether a high-priority task interrupts the current task
  checkForInterruption(fiber, expirationTime);
  // Reports scheduling updates
  recordScheduleUpdate();

  // Get the priority
  const priorityLevel = getCurrentPriorityLevel();
  // Synchronize tasks
  if (expirationTime === Sync) {
    if (
      // render before the first execution
      // Do not batch update(executionContext & LegacyUnbatchedContext) ! == NoContext &&// Is render already available
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // Trace pending interactions registered at root to avoid losing traced interaction data
      schedulePendingInteractions(root, expirationTime);

      // Render is synchronized with batch updates, but layout updates are delayed until the end of batch updates
      // Render the root component
      // Call workLoop for loop unit update
      let callback = renderRoot(root, Sync, true);
      while(callback ! = =null) {
        callback = callback(true);
      }
      / / after the render
    } else {
      // Execute the scheduling task immediately
      scheduleCallbackForRoot(root, ImmediatePriority, Sync);
      // Check if there is no update
      if (executionContext === NoContext) {
        // Refresh the synchronization task queue
        // In scheduleUpdateOnFiber instead of scheduleCallbackForFiber to retain the ability to schedule callbacks without immediately refreshing them.
        // Do this only for user-initiated updates to preserve the historical behavior of synchronous mode.flushSyncCallbackQueue(); }}}else {
    // The asynchronous task executes the scheduling task immediately
    scheduleCallbackForRoot(root, priorityLevel, expirationTime);
  }

  if( (executionContext & DiscreteEventContext) ! == NoContext && (priorityLevel === UserBlockingPriority || priorityLevel === ImmediatePriority) ) {if (rootsWithPendingDiscreteUpdates === null) {
      // If key is root, value is expirationTime
      rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
    } else {
      // Get the latest DiscreteTime
      const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
      / / update the DiscreteTime
      if (lastDiscreteTime === undefined|| lastDiscreteTime > expirationTime) { rootsWithPendingDiscreteUpdates.set(root, expirationTime); }}}}export const scheduleWork = scheduleUpdateOnFiber;
Copy the code

Through the code analysis above we learned the whole fiber creation process and mentioned a little bit about scheduling. How to implement the scheduling mechanism will be discussed in the next article.

conclusion

Through the above code we summarize the following points of knowledge

Realization principle of Fiber

As we know from the above code (although the scheduling process has not been analyzed yet), Fiber actually puts the operations that need to be performed in the Update Ue queue, not the Javascript stack. The entire rendering is controlled by scheduling logical priority control to keep stack frames in memory.

The effect of fiber

  • Each ReactElement corresponds to a Fiber object
  • Record the state of each node, such as props and state
  • It connects the entire application to form a tree structure

fiber-tree

The figure above is the data structure diagram of the whole Fiber-tree, which is formed through the process of recursive diff by splitting it into a series of small tasks and scheduling algorithm.

other

Because the length is too long, so the scheduling process is only about the analysis, but we know the following knowledge points from the above source code

  • The effect of hydrate
  • Batch updates are not done during the first render because of the need to speed up the rendering process
  • What are node fields in FiberNode
  • At initialization create update child nodes by expirationTime expirationTime to determine the batch method
  • Task execution has the highest priority for the Sync task priority > computeInteractiveExpiration user interaction update task > computeAsyncExpiration asynchronous task computing
  • SetState is performed multiple times in 25ms and is merged, as explained in a subsequent article via BatchUpdate
  • All data is placed in the Fiber object’s updateQueue queue waiting for updates
  • ScheduleWork enables scheduling tasks. The priority of scheduling tasks is calculated using the expirationTime