react source code analysis 6 Legacy mode and concurrent mode

Posted by mcirl2 on Mon, 14 Feb 2022 02:46:19 +0100

react source code analysis 6 Legacy mode and concurrent mode

Video Explanation (efficient learning): Enter learning

react start mode

React has three modes to enter the entry of the main function. We can access it from the official document of react Use Concurrent mode (experimental) Three modes are compared:

  • legacy mode: reactdom render(<App />, rootNode). This is how the React app is currently used. There is no plan to delete this mode at present, but this mode may not support these new functions.
  • blocking mode: reactdom createBlockingRoot(rootNode). render(<App />). At present, the experiment is under way. As the first step in migrating to concurrent mode.
  • concurrent mode: reactdom createRoot(rootNode). render(<App />). At present, in the experiment, it is intended to be the default development mode of React after it is stable in the future. This mode opens all new functions.

Characteristic comparison:

legacy mode has the function of automatic batch processing in synthetic events, but it is limited to one browser task. Nonstable must be used for non React events to use this function_ batchedUpdates. In blocking mode and concurrent mode, all setstates are batch processed by default. A warning will be issued during development

The meaning of different modes in react runtime

legacy mode is commonly used by us. The process of building dom is synchronous. Therefore, in render's reconciler, if the diff process is particularly time-consuming, the result is that js has been blocking high priority tasks (such as user click events), which shows that the page is stuck and cannot respond.

concurrent Mode is the future mode of react. It uses time slice scheduling to realize asynchronous and interruptible tasks. The length of time slice is also different according to the performance of the device. In each time slice, if the task reaches the expiration time, it will actively give up the thread to the high priority task. This part will be in the scheduler & lane model in section 15.

The main execution process of the two mode functions

1. Main execution process:

2. Detailed function calling process:

Use demo_0 is clearer with the video debugging. The yellow part is the main task to create fiberRootNode and rootFiber, the red part is to create Update, and the blue part is the entry function of the scheduling render stage

3.legacy mode:

  • Render calls legacyRenderSubtreeIntoContainer, and finally createRootImpl calls to createFiberRoot to create fiberRootNode, and then calls createHostRootFiber to create rootFiber, where fiberRootNode is the root node of the whole project, and rootFiber is the node node hanging by the current application, that is, ReactDOM.. Root node after render call

    //The top-level node is the root node of the whole project, fiberRootNode
    ReactDOM.render(<App />, document.getElementById("root"));//rootFiber
    ReactDOM.render(<App />, document.getElementById("root"));//rootFiber

  • After creating the Fiber node, legacyRenderSubtreeIntoContainer calls updateContainer to create an Update object and mount it on the ring linked list of updateQueue. Then, execute scheduleUpdateOnFiber and call performSyncWorkOnRoot to enter the render stage and commit stage

Concurrent mode:

  • createRoot calls createRootImpl to create fiberRootNode and rootNode
  • After creating the Fiber node, call ReactDOMRoot.. prototype. Render executes updateContainer, and then scheduleUpdateOnFiber asynchronously schedules performcurrentworkonroot to enter the render phase and commit phase

5. Comments on main functions of legacy mode

function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
  //...
  var root = container._reactRootContainer;
  var fiberRoot;

  if (!root) {
    // mount time
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);//Create root node
    fiberRoot = root._internalRoot;

    if (typeof callback === 'function') {//Processing callback
      var originalCallback = callback;

      callback = function () {
        var instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    } 


    unbatchedUpdates(function () {
      updateContainer(children, fiberRoot, parentComponent, callback);//Create update entry
    });
  } else {
    // update
    fiberRoot = root._internalRoot;

    if (typeof callback === 'function') {//Processing callback
      var _originalCallback = callback;

      callback = function () {
        var instance = getPublicRootInstance(fiberRoot);

        _originalCallback.call(instance);
      };
    } 
    
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
}
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
  var root = new FiberRootNode(containerInfo, tag, hydrate);//Create fiberRootNode
  const uninitializedFiber = createHostRootFiber(tag);//Create rootFiber
  //rootFiber and fiberRootNode connections
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;
  //Create updateQueue
  initializeUpdateQueue(uninitializedFiber);
  return root;
}

//For HostRoot or ClassComponent, an updateQueue will be created using initializeUpdateQueue, and then the updateQueue will be mounted on the fiber node
export function initializeUpdateQueue<State>(fiber: Fiber): void {
  const queue: UpdateQueue<State> = {
    baseState: fiber.memoizedState,//The initial state. Based on this state, the new state will be calculated according to Update
    firstBaseUpdate: null,//The header of the linked list formed by Update
    lastBaseUpdate: null,//The tail of the linked list formed by Update
        //The newly generated update will be saved in shared. Net as a one-way circular linked list On pending, when calculating the state, the circular linked list will be cut off and connected after / / lastBaseUpdate
    shared: {
      pending: null,
    },
    effects: null,
  };
  fiber.updateQueue = queue;
}
function updateContainer(element, container, parentComponent, callback) {
  var lane = requestUpdateLane(current$1);//Get the currently available lane and explain it in Chapter 12
  var update = createUpdate(eventTime, lane); //Create update

  update.payload = {
    element: element//jsx
  };

  enqueueUpdate(current$1, update);//update join the team
  scheduleUpdateOnFiber(current$1, lane, eventTime);//Schedule update
  return lane;
}
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
  if (lane === SyncLane) {//Synchronous lane corresponding legacy mode
    //...
    performSyncWorkOnRoot(root);//The starting point of the render phase is explained in Chapter 6
  } else {//concurrent mode
    //...
    ensureRootIsScheduled(root, eventTime);//Ensure that root is scheduled
  } 
}

6. Notes to concurrent main functions:

function ensureRootIsScheduled(root, currentTime) {
  //...
  var nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes); //Calculate nextLanes

  //...

 //Convert the priority of lane to that of Schuller
  var schedulerPriorityLevel = lanePriorityToSchedulerPriority(newCallbackPriority);
  //Execute performcurrentworkonroot with the priority of schedulerPriorityLevel, which is the starting point of concurrent mode
  newCallbackNode =       scheduleCallback(schedulerPriorityLevel,performConcurrentWorkOnRoot.bind(null, root));
}

7. Differences between the two modes:

  1. The second parameter passed in createRootImpl is different. One is LegacyRoot and the other is ConcurrentRoot
  2. The priority of lane obtained in requestUpdateLane is different
  3. In the function scheduleUpdateOnFiber, different branches are entered according to different priorities. The legacy mode enters performSyncWorkOnRoot, and the concurrent mode will asynchronously schedule performcurrentworkonroot

Topics: React