React source code

5. State update process (what’s going on in setState)

Video lessons & Debug demos

The purpose of the video course is to quickly master the process of running the React source code and scheduler, Reconciler, Renderer, Fiber, etc., in react, and debug the source code and analysis in detail to make the process clearer.

Video course: Enter the course

Demos: demo

Course Structure:

  1. You’re still struggling with the React source code.
  2. The React Mental Model
  3. Fiber(I’m dom in memory)
  4. Start with Legacy or Concurrent (start at the entrance and let’s move on to the future)
  5. State update process (what’s going on in setState)
  6. Render phase (awesome, I have the skill to create Fiber)
  7. Commit phase (I heard renderer marked it for us, let’s map real nodes)
  8. Diff algorithm (Mom doesn’t worry about my Diff interviews anymore)
  9. Function Component saves state
  10. Scheduler&lane model (to see tasks are paused, continued, and queue-jumped)
  11. What is concurrent mode?
  12. Handwriting mini React (Short and Sharp is me)

In the last section, we introduced the process from the react entry function to the render phase, which is the first render process of mount. In this section, we introduce the process from the render phase after updating the state.

React triggers status updates in several ways:

  • ReactDOM.render

  • this.setState

  • this.forceUpdate

  • useState

  • useReducer

    Let’s focus on this.setState and this.forceUpdate. Hook in Chapter 11

    1. This. Within call setState enclosing updater. EnqueueSetState

    Component.prototype.setState = function (partialState, callback) {
      if(! (typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {{throw Error( "setState(...) : takes an object of state variables to update or a function which returns an object of state variables."); }}this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };
    Copy the code

    2. This. forceUpdate is the same as this.setState, except that forceUpdate is assigned to tag

    enqueueForceUpdate(inst, callback) {
        const fiber = getInstance(inst);
        const eventTime = requestEventTime();
        const suspenseConfig = requestCurrentSuspenseConfig();
        const lane = requestUpdateLane(fiber, suspenseConfig);
    
        const update = createUpdate(eventTime, lane, suspenseConfig);
        
        / / tag assignment ForceUpdate
        update.tag = ForceUpdate;
        
        if(callback ! = =undefined&& callback ! = =null) { update.callback = callback; } enqueueUpdate(fiber, update); scheduleUpdateOnFiber(fiber, lane, eventTime); }};Copy the code

    If mark ForceUpdate, render phase component will update according to checkHasForceUpdateAfterProcessing, judging and checkShouldComponentUpdate, If the Update of the tag is ForceUpdate checkHasForceUpdateAfterProcessing to true, when the component is PureComponent, CheckShouldComponentUpdate shallow comparison state and props, so that when using this. ForceUpdate will be updated

    const shouldUpdate =
      checkHasForceUpdateAfterProcessing() ||
      checkShouldComponentUpdate(
        workInProgress,
        ctor,
        oldProps,
        newProps,
        oldState,
        newState,
        nextContext,
      );
    Copy the code

    3. EnqueueForceUpdate creates updates, schedules updates, etc

    enqueueSetState(inst, payload, callback) {
      const fiber = getInstance(inst);/ / fiber instance
    
      const eventTime = requestEventTime();
      const suspenseConfig = requestCurrentSuspenseConfig();
      
      const lane = requestUpdateLane(fiber, suspenseConfig);/ / priority
    
      const update = createUpdate(eventTime, lane, suspenseConfig);/ / create the update
    
      update.payload = payload;
    
      if(callback ! = =undefined&& callback ! = =null) {  // Assign callback
        update.callback = callback;
      }
    
      enqueueUpdate(fiber, update);/ / update to join updateQueue
      scheduleUpdateOnFiber(fiber, lane, eventTime);/ / schedule update
    }
    
    Copy the code

    Overall status update process

    Create the Update

    When HostRoot or ClassComponent triggers an update, the update is created in createUpdate and evaluated in the beginWork in the Render phase that follows. The FunctionComponent counterpart, described in Chapter 11, is a bit different from HostRoot’s or ClassComponent’s Update structure

    export function createUpdate(eventTime: number, lane: Lane) :Update< * >{/ / create the update
      const update: Update<*> = {
        eventTime,
        lane,
    
        tag: UpdateState,
        payload: null.callback: null.next: null};return update;
    }
    Copy the code

    We mainly focus on the result parameters:

    • Lane: Priorities (Chapter 12)

    • Tag: Indicates the update type, such as UpdateState or ReplaceState

    • Payload: The payload of the ClassComponent payload is the first parameter of setState, and the payload of the HostRoot payload is the first parameter of reactdom.render

    • Callback: The second argument to setState

    • Next: Join the next Update to form a linked list, such as multiple updates when multiple SETStates are fired at the same time, and then join with next

    UpdateQueue:

    InitializeUpdateQueue is used to create updateQueue for HostRoot or ClassComponent at mount time and then mount updateQueue to fiber node

    export function initializeUpdateQueue<State> (fiber: Fiber) :void {
      const queue: UpdateQueue<State> = {
        baseState: fiber.memoizedState,
        firstBaseUpdate: null.lastBaseUpdate: null.shared: {
          pending: null,},effects: null}; fiber.updateQueue = queue; }Copy the code
  • BaseState: the initial state, which will be used to calculate the new state based on the Update

    • FirstBaseUpdate and lastBaseUpdate: Start and end of the linked list formed by Update
    • Shared. pending: New updates are stored on shared.pending as a one-way loop, which is clipped when calculating state and linked to lastBaseUpdate
    • Effects: Calback is not null update

    Traverse up from the Fiber node to rootFiber

    In markUpdateLaneFromFiberToRoot function will traverse upward from the triggered update nodes start to rootFiber, traverse all the nodes will deal with the process of priority (chapter 12)

      function markUpdateLaneFromFiberToRoot(sourceFiber: Fiber, lane: Lane,) :FiberRoot | null {
        sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
        let alternate = sourceFiber.alternate;
        if(alternate ! = =null) {
          alternate.lanes = mergeLanes(alternate.lanes, lane);
        }
        let node = sourceFiber;
        let parent = sourceFiber.return;
        while(parent ! = =null) {// Start from the node that triggered the update and traverse up to rootFiber
          parent.childLanes = mergeLanes(parent.childLanes, lane);// Merge childLanes priorities
          alternate = parent.alternate;
          if(alternate ! = =null) {
            alternate.childLanes = mergeLanes(alternate.childLanes, lane);
          } else {
          }
          node = parent;
          parent = parent.return;
        }
        if (node.tag === HostRoot) {
          const root: FiberRoot = node.stateNode;
          return root;
        } else {
          return null; }}Copy the code

scheduling

In ensureRootIsScheduled scheduleCallback will begin with a priority scheduling render phase function performSyncWorkOnRoot or performConcurrentWorkOnRoot

if (newCallbackPriority === SyncLanePriority) {
  // The task has expired and the render phase needs to be executed synchronously
  newCallbackNode = scheduleSyncCallback(
    performSyncWorkOnRoot.bind(null, root)
  );
} else {
  // Execute the Render phase asynchronously based on task priority
  var schedulerPriorityLevel = lanePriorityToSchedulerPriority(
    newCallbackPriority
  );
  newCallbackNode = scheduleCallback(
    schedulerPriorityLevel,
    performConcurrentWorkOnRoot.bind(null, root)
  );
}
Copy the code

Status updates

The classComponent state calculation takes place in the processUpdateQueue function, which involves a lot of list operations

  • The initial fiber. UpdateQueue single linked list has firstBaseUpdate (update1) and lastBaseUpdate (update2), connected by next

  • Fiber. UpdateQueue. Shared loop contains update3 and update4, which are connected to each other by the next link

  • When calculating state, first ‘cut’ the circular linked list fibre.updatequeue. Shared to form a single linked list, which is connected after Fibre.updatequeue to form baseUpdate

  • Then iterate through this list and calculate memoizedState from Base Estate

Status updates with priority

Similar to Git commits, c3 means high-priority tasks, such as user-initiated events, data requests, synchronized code execution, etc.

  • An application created with reactdom.render has no concept of priority. It is equivalent to committing git first and then committing C3

  • In concurrent mode, similar to Git rebase, the previous code is temporarily stored, developed on master, and then rebase to the previous branch

    Priorities are scheduled by the Scheduler. Here we only care about the priority order of the state calculation, that is, the calculation that takes place in the function processUpdateQueue. For example, there are four updates from C1 to C4 initially, where C1 and C3 are of high priority

    • On the first render, low-priority updates are skipped, so only C1 and C3 are added to the state calculation

    • Update (c1) before the first skipped update (c2) is recalculated as baseUpdate after the second render

      In concurrent mode, componentWillMount may be executed multiple times, settling inconsistencies with previous releases

      Note that fiber.updatequue. Shared exists in both workInprogress fiber and Current Fiber to prevent state loss due to high-priority interruption of ongoing calculations. This code also happens in processUpdateQueue