Update the React

1. Three update modes

ReactDOM.render || hydrate

setState

forceUpdate

The react-dom/client file contains the following files

2. Data structure of fiberRoot object

1. FiberRoot The starting point of the entire application contains information about the mounted target node that records the entire application update process

//ReactFiberRoot.js
export function createFiberRoot(
  containerInfo: any,/ /! Dom node of a real Container
  isConcurrent: boolean,
  hydrate: boolean,
) :FiberRoot {
  // Cyclic construction. This cheats the type system right now because
  // stateNode is any.
  const uninitializedFiber = createHostRootFiber(isConcurrent);/ /! It's a fiber object because the root node can also update this fiber is the node of the whole fiber tree

  let root;
  if (enableSchedulerTracing) {
    root = ({
      current: uninitializedFiber,/ /! Fiber object corresponding to the root node
      containerInfo: containerInfo,/ /! Reactdom. render The real DOM node passed in
      pendingChildren: null./ /! Do not use persistent updates
      / /! The following time is the time marked when the task is scheduled
      earliestPendingTime: NoWork,
      latestPendingTime: NoWork,
      earliestSuspendedTime: NoWork,
      latestSuspendedTime: NoWork,
      latestPingedTime: NoWork,
      / /! Flag whether errors occur during application rendering
      didError: false./ /! A ExpirationTime for a task that is waiting to be submitted
      pendingCommitExpirationTime: NoWork,
      / /! If only one fiberRoot object has been completed, it can only be fiber or NULL corresponding to that Root
      finishedWork: null./ /! Suspense returns content for setTimeout Settings when tasks are suspended
      timeoutHandle: noTimeout,
      / /! Not how to use
      context: null.pendingContext: null./ /! Whether dom nodes need to be merged
      hydrate,
      / /! So when we're rendering this update, we're going to iterate over every expirationTime of every node and then this is the highest expirationTime of the root. If you would like to iterate over a expirationTime of a node, then you would not update this node
      nextExpirationTimeToWorkOn: NoWork,
      / /! The scheduled time is updated with the corresponding expiration time
      expirationTime: NoWork,
      / /! useless
      firstBatch: null./ /! Creating a one-way linked list structure can have multiple FiberRoots
      nextScheduledRoot: null.interactionThreadID: unstable_getThreadID(),
      memoizedInteractions: new Set(),
      pendingInteractionMap: new Map(),
    }: FiberRoot);
  } else {
    root = ({
      current: uninitializedFiber,
      containerInfo: containerInfo,
      pendingChildren: null.earliestPendingTime: NoWork,
      latestPendingTime: NoWork,
      earliestSuspendedTime: NoWork,
      latestSuspendedTime: NoWork,
      latestPingedTime: NoWork,

      didError: false.pendingCommitExpirationTime: NoWork,
      finishedWork: null.timeoutHandle: noTimeout,
      context: null.pendingContext: null,
      hydrate,
      nextExpirationTimeToWorkOn: NoWork,
      expirationTime: NoWork,
      firstBatch: null.nextScheduledRoot: null,
    }: BaseFiberRootProperties);
  }
Copy the code

3. Fiber object data structure

Each reactElement corresponds to a Fiber object, which is used to record various node states, such as props. Fiber will be updated to the props and state of this. ClassComponent. Fiber is also connected to the entire application

//ReactFiber.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.

  // Tag identifying the type of fiber.
  tag: WorkTag,/ /! Different component types have native class Component and function Component with different properties and update methods

  // Unique identifier of this child.
  key: null | string,/ /! The React. The key element

  // The value of element.type which is used to preserve the identity during
  // reconciliation of this child.
  elementType: any,/ /! It's react.element. type so there's context, refProvider, etc, $$type in there

  // The resolved function/class/ associated with this fiber.
  type: any,/ /! Log whether the asynchronous component reslove is a function component or a class component (lazy component)

  // The local state associated with this fiber.
  / /! An instance of a node such as a class component is null and a DOM node is a DOM node
  / /! Once we have this property, we can put the updated data of our node property onto this object
  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

  // The Fiber to return to after finishing processing this one.
  // This is effectively the parent, but there can be multiple parents (two)
  // so this is only the parent of the thing we're currently processing.
  // It is conceptually the same as the return address of a stack frame.
  / /! The following three properties are used to build the Fiber tree
  return: Fiber | null./ /! Point to the parent node

  // Singly Linked List Tree Structure.
  child: Fiber | null./ /! Point to child node
  sibling: Fiber | null./ /! Point to sibling nodes
  index: number,

  // 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,/ /! The ref that comes in

  // Input is the data coming into process this fiber. Arguments. Props.
  / /! An application update like setState is going to generate a new props that's going to store that property
  pendingProps: any, // This type will be more specific once we overload the tag.
  / /! The old props was last updated
  memoizedProps: any, // The props used to create the output.

  // A queue of state updates and callbacks.
  / /! The update queue records all updates that have been updated
  updateQueue: UpdateQueue<any> | null.// The state used to create the output
  / /! Update the old state before
  memoizedState: any,

  // A linked-list of contexts that this fiber depends on
  / /! The context related
  firstContextDependency: ContextDependency<mixed> | null.// 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.
  / /! ConcurrentMode and strictMode are equal to the mode of the parent node
  mode: TypeOfMode,
  / /! Record side effects
  // Effect
  effectTag: SideEffectTag,

  // Singly linked list fast path to the next fiber with side-effects.
  nextEffect: Fiber | null.// The first and last fiber with side-effect within this subtree. This allows
  // us to reuse a slice of the linked list when we reuse the work done within
  // this fiber.
  firstEffect: Fiber | null.lastEffect: Fiber | null.// Represents a time in the future by which this work should be completed.
  // Does not include work found in its subtree.
  / /! Expiration time of node update
  expirationTime: ExpirationTime,
  / /! The update expiration time of the child node
  // This is used to quickly determine if a subtree has no pending changes.
  childExpirationTime: ExpirationTime,

  // 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.
  / /! <==>woringProgress current is the old fiber object and woringProgress is the new fiber object. current.alternate = working-fiber working-fiber.alternate = current
  alternate: Fiber | null./ /! Node rendering time is related
  // 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 descedents 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, |};Copy the code

Fiber figure

Rounding out the alternate

For the first rendering, the rendered fiber node tree is called current Tree. When the rendering is updated, the fiber node will be reused, the current tree will remain unchanged, and the update content will be reflected as another Fiber node tree work-in-Progress tree. When all rendering processes are complete, the work-in-Progress tree is refreshed to the page and becomes a new current tree (current tree represents rendered content; Work-in-progress tree represents render content in process). Fiber nodes in the current tree and work-in-Progress tree hold each other with alternate properties, so that the processing mode of pairing is convenient to delay batch application of updates.

Summary: There are two fibers in the update process, one is fiber1(Current) before the update and the other is Fiber2 (working-Fiber) after the update.

Alternate = Fiber1. Alternate = Fiber2. Alternate = Fiber1 then current becomes working-fiber for subsequent update.

4.update

The function that records component state changes is stored in the Fiber object’s updateQueue. Multiple updates can exist simultaneously, such as setState() three times in a row. We’ll figure out the final state and only update it once

//react-dom/ReactUpdateQueue.js

//update
 {
    / /! ExpirationTime expirationTime of this update
    expirationTime: expirationTime,
    / /! 0= update state 1= replace state 2= force update 3= catch render error and update to the UI we need to return when we catch render error
    tag: UpdateState,
    / /! The actual content of the update operation, if it's the first time it's passed in the children of the Reactlist and if it's setState the payload is passed in as a callback for state
    payload: null.callback: null.next: null./ /! The next update update ue is a one-way linked list structure
    nextEffect: null./ /! Next side effect
  };
  
  //updateQueue
  export type UpdateQueue<State> = {
  / /! Count all the updates in the queue and then calculate a new state that will be updated next time
  baseState: State,
  / /! The first update in the queue
  firstUpdate: Update<State> | null.lastUpdate: Update<State> | null./ /! The first update to catch the wrong type
  firstCapturedUpdate: Update<State> | null.lastCapturedUpdate: Update<State> | null.firstEffect: Update<State> | null.lastEffect: Update<State> | null.firstCapturedEffect: Update<State> | null.lastCapturedEffect: Update<State> | null};//enqueueUpdate
Copy the code

5. ExpirationTime calculation

//ReactFiberScheduler.js
Copy the code
 / /! A high-priority expirationTime has two different constants
        expirationTime = computeInteractiveExpiration(currentTime);
      } else {
        // This is an async update
        / /! A low-priority expirationTime has two different constants
        expirationTime = computeAsyncExpiration(currentTime);
Copy the code

**React has two types of ExpirationTime. For example, if the response is triggered by an event, its priority will be higher because of the interaction involved. ** So asynchronous tasks are low and can be interrupted

export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;
// The low priorities are 5000 and 250
export function computeAsyncExpiration(
  currentTime: ExpirationTime,
) :ExpirationTime {
  return computeExpirationBucket(
    currentTime,
    LOW_PRIORITY_EXPIRATION,
    LOW_PRIORITY_BATCH_SIZE,
  );
}
export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
export const HIGH_PRIORITY_BATCH_SIZE = 100;
// The high priority is 500 100
export function computeInteractiveExpiration(currentTime: ExpirationTime) {
  return computeExpirationBucket(
    currentTime,
    HIGH_PRIORITY_EXPIRATION,
    HIGH_PRIORITY_BATCH_SIZE,
  );
}
function ceiling(num: number, precision: number) :number {
  return (((num / precision) | 0) + 1) * precision;
}

function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs,) :ExpirationTime {
  return (
    MAGIC_NUMBER_OFFSET +
    ceiling(
      currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE,
      bucketSizeMs / UNIT_SIZE,
    )
  );
}
// You would like to see an ExpirationTime
export function msToExpirationTime(ms: number) :ExpirationTime {
  // Always add an offset so that we don't clash with the magic number for NoWork.
  return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET;
}
// You would like to see an ExpirationTime
export function expirationTimeToMs(expirationTime: ExpirationTime) :number {
  return (expirationTime - MAGIC_NUMBER_OFFSET) * UNIT_SIZE;
}
Copy the code

The final formula is the two numbers in bold that represent the change

((((currentTime – 2 + 5000 / 10) / 25) | 0) + 1) * 25

Minus 2 is just because msToExpirationTime adds a 2 to both of them

The key is there is also a step integer | 0 means integer

That is, your currentTime changes within 250ms and this value stays the same 250 corresponds to a Batch size

This is 25 because = LOW_PRIORITY_BATCH_SIZE(250)/ UNIT_SIZE(10) but why is currentTime within 250 because you go to MS and currentTime has removed UNIT_SIZE(10) So these two times are equivalent to ms÷250

This is probably done so that two very similar updates (setState) get the same expirationTime, with the same priority, and then complete in one update, equivalent to an automatic batch update

6. The meaning of expirationTime

We know that there are two types of expirationTime and the first type is a context event and the second type is an asynchronous task but if you add an asynchronous task that keeps being interrupted by some high-priority task and you wait longer than your expirationTime then that task will be forced So a high-priority task would have a smaller expirationTime, which would force it to wait less

Data structure relationship combing

Fiber has a context update that contains updates. Update contains context Time and payload.

7. Different expirationTime

1. Sync Synchronization mode: The system is created when the priority is the highest

2. Asynchronous mode: Task scheduling according to the expirationTime task scheduling is mainly divided into two types of trigger events and ordinary asynchronous tasks (trigger events have a higher priority than ordinary)

3. Specify the context with the highest priority

Context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context = context

function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
  let expirationTime;
  / /! ExpirationContext is just a expirationTime that starts with nowork=0
  / /! Context = context = context = context = context = context = context = context = context = context = context Context=sync = expirationContext=sync = {context = expirationContext=sync = {context = expirationContext=sync = {context = expirationContext=sync}
  if(expirationContext ! == NoWork) {// An explicit expiration context was set;
    / /! ExpirationContext = computeAsyncExpiration(currentTime); expirationContext = computeAsyncExpiration(currentTime)
    / /! ExpirationContext = Sync if you want to expirationContext = Sync if you want to expirationTime=expirationContext Sync makes the callback task the highest priority
    expirationTime = expirationContext;
  } else if (isWorking) {
    / /! There are tasks that are being updated and there are external forces that specify expirationTime
    if (isCommitting) {
      // Updates that occur during the commit phase should have sync priority
      // by default.
      expirationTime = Sync;
    } else {
      // Updates during the render phase should expire at the same time as
      // the work that is being rendered.expirationTime = nextRenderExpirationTime; }}else {
    / /! In the absence of external coercion
    // No explicit expiration context was set, and we're not currently
    // performing work. Calculate a new expiration time.
    / /! If the Mode is in ConcurrentMode, you will calculate the expirationTime
    if (fiber.mode & ConcurrentMode) {
      if (isBatchingInteractiveUpdates) {/ /! This value is true for callback functions that use high/low priority for most events
        // This is an interactive update
        / /! A high-priority expirationTime has a constant different event triggers tasks of a higher priority
        expirationTime = computeInteractiveExpiration(currentTime);
      } else {
        // This is an async update
        / /! A low priority expirationTime has a constant different from a normal asynchronous task with a low priority
        expirationTime = computeAsyncExpiration(currentTime);
      }
      // If we're in the middle of rendering a tree, do not update at the same
      // expiration time that is already rendering.
      if(nextRoot ! = =null && expirationTime === nextRenderExpirationTime) {
        expirationTime += 1;/ /! To distinguish between this update and the next}}else {
      / /! Set to sync instead of ConcurrentMode to synchronize updates
      // This is a sync updateexpirationTime = Sync; }}if (isBatchingInteractiveUpdates) {
    // This is an interactive update. Keep track of the lowest pending
    // interactive expiration time. This allows us to synchronously flush
    // all interactive updates when needed.
    if(expirationTime > lowestPriorityPendingInteractiveExpirationTime) { lowestPriorityPendingInteractiveExpirationTime = expirationTime; }}return expirationTime;
}
Copy the code

React fiber mode binary represents different values of 1. The advantage is that we can use and or to quickly determine which modes are in the fiber binary

/ /! The react Fiber type binary has the advantage of using and and to quickly determine which modes are present

export type TypeOfMode = number;

export const NoContext = 0b000;//0b stands for binary
export const ConcurrentMode = 0b001;
export const StrictMode = 0b010;
export const ProfileMode = 0b100;
/ /! Binary explanationFor example, a =000 b=001 c=010 d=100
letMode = a mode&a returns0It means that no mode a returns1Has this pattern mode = mode | b this operation said the b this pattern to join in Mode = at this time001Mode&b return1Mode = mode mode = | c at this time011 mode&c=1
Copy the code

8. SetState & forceUpdate to update components

Fiber for nodes to create updates but their update types are different with different tags

/ /! The core logic of the React class component
const classComponentUpdater = {
  isMounted,
  / /! The payload is the setState callback function
  enqueueSetState(inst, payload, callback) {
    / /! This ReactInstanceMap is the mapping between Fiber and INST
    const fiber = ReactInstanceMap.get(inst);
    / /! Calculate currentTime and expirationTime and Update
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);
    update.payload = payload;/ /! The setState update function of the class component is the callback to updateContainer. Payload is the child child
    if(callback ! = =undefined&& callback ! = =null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      update.callback = callback;/ /! Set callback to update if there is a callback
    }

    enqueueUpdate(fiber, update);/ /! Join the queue
    scheduleWork(fiber, expirationTime);/ /! Start Task Scheduling
  },
  enqueueReplaceState(inst, payload, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);
    update.tag = ReplaceState;
    update.payload = payload;

    if(callback ! = =undefined&& callback ! = =null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'replaceState');
      }
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
  enqueueForceUpdate(inst, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);
    update.tag = ForceUpdate;/ /! The only difference is that the tag is ForceUpdate

    if(callback ! = =undefined&& callback ! = =null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'forceUpdate'); } update.callback = callback; } enqueueUpdate(fiber, update); scheduleWork(fiber, expirationTime); }};Copy the code

The flow chart

Q: We calculate expirationTime passed in fiber and currentTime so the expirationTime calculated is one-to-one with fiber, but a fiber a component has different tasks inside how to distinguish priority.