Update logic

  1. inFiberTo create aUpdate.
    1. Setting related Propertieslane,eventTime,payload,callback.
    2. Function:createUpdate->enqueueUpdate
  2. throughSchedulerScheduling schedules rendering.
    1. Confirm the render taskperformConcurrentWorkOnRootorperformSyncWorkOnRootThe priority and execution time of.
    2. Function:scheduleUpdateOnFiber-> ensureRootIsScheduled

The data structure

Class Component

UpdateQueue A circular list of Update nodes. SharedQueue is the end of the list.

type Update<State> = {
  // Event time, which will be removed later, will store the transition -> event time mapping in the root directory.
  eventTime: number./ / priority
  lane: Lane,
   // Update type
  // Corresponding to UpdateState, ReplaceState, ForceUpdate, and CaptureUpdate
  tag: 0 | 1 | 2 | 3.// newState or newState function
  payload: any.callback: (() = > mixed) | null.// Next update
  next: Update<State> | null};type UpdateQueue<State> = {
  // update list
  shared: SharedQueue<State>, 
  // The following three values are the status of the interrupted protection site.
  // Update the value before interruption when interrupted by a high priority.
  baseState: State, 
  firstBaseUpdate: Update<State> | null.lastBaseUpdate: Update<State> | null./ / side effects
  effects: Array<Update<State>> | null};type SharedQueue<State> = {
  // The end of the update ue is a circular linked list
  pending: Update<State> | null.// merge UpdateQueue priority to UpdateQueue lanes
  lanes: Lanes,
  interleaved: Update<State> | null};type Fiber = {
  // state holds values
  memoizedState: any
  // UpdateQueue
  updateQueue:UpdateQueue
}
Copy the code

Function Component

The state of function components is managed by Hooks, so each Hooks has its own UpdateQueue.

type Update<S, A> = {
  lane: Lane,  / / priority
  action: A,   // newState or newState function
  hasEagerState: boolean./ / state optimization
  eagerState: S | null./ / state optimization
  next: Update<S, A>,     // Next update
};

type UpdateQueue<S, A> = {
  // Update list is a circular linked list
  pending: Update<S, A> | null.// merge UpdateQueue priority to UpdateQueue lanes
  lanes: Lanes,
  // Corresponding dispatch function
  dispatch: (A= > mixed) | null.// Assign the reducer function again each time the reducer function is passed in using useReducer. It is up to date every time it is used.
  lastRenderedReducer: ((S, A) = > S) | null.// The state calculated by hook was updated last time.
  lastRenderedState: S | null.interleaved: Update<S, A> | null};type Hook = {
  // state
  memoizedState: any.// UpdateQueue
  queue: any.// Next hook
  next: Hook | null.// The following two values are the status of the interrupted protection site.
  // Update the value before interruption when interrupted by a high priority.
  baseState: any.// UpdateQueue before interruption
  baseQueue: Update<any.any> | null};type Fiber = {
  // state stores the end of the hook list. A circular linked list
  memoizedState: Hook
  // Hook has its own updateQueue,
  // Fiber update ue is currently useless.
}
Copy the code

How to trigger an update

ReactDOM.render

Update has the same data structure as ClassCompoent.

updateContainer

Reactdom.render and reactdom.createroot ().render both call updateContainer.

// reactdom.render (element) and reactdom.createroot (element). Render both call updateContainer.
export function updateContainer(element: ReactNodeList, container: OpaqueRoot, parentComponent: ? React$Component<any, any>, callback: ?Function.) :Lane {
  / / current is FiberRoot
  const current = container.current;
  / / time
  const eventTime = requestEventTime();
  / / priority
  const lane = requestUpdateLane(current);

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }
   // Create update, ClassComponent.
  const update = createUpdate(eventTime, lane);
  // The FiberRoot payload is the mounted DOM node
  React DevTools currently relies on this property
  update.payload = {element};
   
  callback = callback === undefined ? null : callback;
  if(callback ! = =null) {
    update.callback = callback;
  }
  // Add Update to the end of the Fiber. Update ue queue.
  enqueueUpdate(current, update, lane);
  // Schedule update tasks
  const root = scheduleUpdateOnFiber(current, lane, eventTime);
  // Scheduling priorities are related
  if(root ! = =null) {
    entangleTransitions(root, current, lane);
  }
  return lane;
}
Copy the code

this.setState — Class Component

State operations for ClassComponent are provided by a classComponentUpdater object. Instance. updater = classComponentUpdater is added to the instance when the component is created.

enqueueSetState

// this.setState actually calls the function
enqueueSetState(inst, payload, callback) {
  // Get the fiber corresponding to the component
  const fiber = getInstance(inst);
  const eventTime = requestEventTime();
  const lane = requestUpdateLane(fiber);
  // Create an Update. Assign the values lane, eventTime, payload, and callback
  const update = createUpdate(eventTime, lane);
  update.payload = payload;
  update.callback = callback;
  // Add Update to the end of the Fiber. Update ue queue.
  enqueueUpdate(fiber, update, lane);
  // Schedule update tasks
  const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
  // Scheduling priorities are related
  if(root ! = =null) { entangleTransitions(root, fiber, lane); }}Copy the code

this.fourceUpdate — Class Component

Basically the same as this.setState. Only the UPDATE data changes slightly

// this.fourceUpdate actually calls the function
// Difference: payload cannot be passed in
enqueueForceUpdate(inst, callback) {
  const fiber = getInstance(inst);
  const eventTime = requestEventTime();
  const lane = requestUpdateLane(fiber);
  
  // Difference: Payload is null. (Default: null when created)
  const update = createUpdate(eventTime, lane);
  // Difference: update tag ForceUpdate
   update.tag = ForceUpdate;
  
  enqueueUpdate(fiber, update, lane);
  const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
  if(root ! = =null) { entangleTransitions(root, fiber, lane); }}Copy the code

useReducer — Function Component

The new version differentiates useReducer from useState because the old version has some bugs.

dispatchReducerAction

function dispatchReducerAction<S.A> (fiber: Fiber, queue: UpdateQueue
       
        , action: A,
       ,>) {
  const lane = requestUpdateLane(fiber);
  // update
  const update: Update<S, A> = {
    lane, / / priority
    action,
    hasEagerState: false.eagerState: null.next: (null: any),
  };
   // If fiber is render
  // Render means that the update is triggered when the component function executes.
  if (isRenderPhaseUpdate(fiber)) {
    
       Dispatch (1) directly within the component, which is immediately triggered by component render.
    //
    // After the component function completes,
    // Call this component function repeatedly.
    // No update is triggered until the component is called.
    // If too many times are triggered, an error is reported.
    // The handling logic is in: function component render function renderWithHooks.
    
    // Add Update to the end of fiber.memoizedState (Hook).queue.
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    // Add Update to the end of fiber.memoizedState (Hook).queue.
    enqueueUpdate(fiber, queue, update, lane);
    const eventTime = requestEventTime();
    
    // Schedule update tasks
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    // Scheduling priorities are related
    if(root ! = =null) { entangleTransitionUpdate(root, queue, lane); }}}Copy the code

useState — Function Component

The new version differentiates useReducer from useState because the old version has some bugs.

Update data structure

// eg: dispatch( n => n+1 )
const update: Update<S, A> = {
  lane,  / / priority
  action,// dispatch Send parameters eg: n => n+1
  hasEagerState: false.// 
  eagerState: null.// 
  next: (null: any),//next update
};
Copy the code

dispatchSetState


function dispatchSetState<S.A> (fiber: Fiber, queue: UpdateQueue
       
        , action: A,
       ,>) {
  const lane = requestUpdateLane(fiber);
  const update: Update<S, A> = {
    lane,
    action,
    hasEagerState: false.eagerState: null.next: (null: any),
  };

  // If fiber is render
  // Render means that the update is triggered when the component function executes.
  if (isRenderPhaseUpdate(fiber)) {
    
       Dispatch (1) directly within the component, which is immediately triggered by component render.
    //
    // After the component function completes,
    // Call this component function repeatedly.
    // No update is triggered until the component is called.
    // If too many times are triggered, an error is reported.
    // The handling logic is in: function component render function renderWithHooks.
    
    // Add Update to the end of fiber.memoizedState (Hook).queue.
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    // Add Update to the end of fiber.memoizedState (Hook).queue.
    enqueueUpdate(fiber, queue, update, lane);
   
    const alternate = fiber.alternate;
    if (
      Fiber is not updated. This is the first update in this round.
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      // Calculate state directly, since it is the first update, regardless of the other logic of priority.
      // This is optimized to minimize state scheduling.
      
      const lastRenderedReducer = queue.lastRenderedReducer;
      if(lastRenderedReducer ! = =null) {
        let prevDispatcher;
        try {
          // State of the last useReducer.
          const currentState: S = (queue.lastRenderedState: any);
          // Calculate the state of this time
          const eagerState = lastRenderedReducer(currentState, action);
          // Record whether the optimization logic was executed.
          update.hasEagerState = true;
          update.eagerState = eagerState;
                      
          // The new state is the same as the old state.
          if (is(eagerState, currentState)) { return; }}catch (error) {
          // Suppress the error. It will throw again in the render phase.}}}const eventTime = requestEventTime();
    // Schedule update tasks
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    // Scheduling priorities are related
    if(root ! = =null) { entangleTransitionUpdate(root, queue, lane); }}}Copy the code

State update formula in concurrent mode

BaseState + Update1 + Updata2 + (Update with sufficient priority) = newState

  • BaseState: after the last updatestateValue, if interrupted by a high priority, is the value calculated before the interrupt occurred.
  • It will filter out the low priorityUpdate.

Arrange the rendering

Schedule rendering tasks based on update and react running status.

scheduleUpdateOnFiber

Schedule updates on Fiber.

export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,
  eventTime: number.) :FiberRoot | null {
   // Merge lane into all fibers on the path from this fiber to Root.
  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  if (root === null) { return null; }
  // The tag root has a pending update
  markRootUpdated(root, lane, eventTime);

  if (
    // Render, and root is the same as root that is working.(executionContext & RenderContext) ! == NoLanes && root === workInProgressRoot ) {// Share the current render. The merge lane.
    workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
      workInProgressRootRenderPhaseUpdatedLanes,
      lane,
    );
  } else {
    // Schedule a rendering task
    ensureRootIsScheduled(root, eventTime);
    if (
      lane === SyncLane && // Synchronization priority
      executionContext === NoContext && // Execute context, no task
      (fiber.mode & ConcurrentMode) === NoMode && // Not concurrent mode
    ) {
      // Compatible with correct rendering of Leacy mode.
      // if the interface is asynchronous, such as setTimeout, call setState, this logic will be used.
      // Render directly.flushSyncCallbacksOnlyInLegacyMode(); }}return root;
}
Copy the code

ensureRootIsScheduled

Schedule performSyncWorkOnRoot for root (render)

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  // Save, a scheduler unit, a task created by the scheduler that is associated with scheduling in concurrent mode.
  const existingCallbackNode = root.callbackNode;
   // If any lane starved, marked expired.
  markStarvedLanesAsExpired(root, currentTime);
   // Find the lane with the highest priority
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
   
  / / no task
  if (nextLanes === NoLanes) {
    if(existingCallbackNode ! = =null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
  }
   // Get the priority according to lane
  const newCallbackPriority = getHighestPriorityLane(nextLanes);

  const existingCallbackPriority = root.callbackPriority;
  if (
     // Check if there is an ongoing task, reuse it with the same priority.
    existingCallbackPriority === newCallbackPriority &&
  ) {
    return;
  }
  
  if(existingCallbackNode ! =null) {
    // Cancel the existing callback. We will arrange a new one below.
    cancelCallback(existingCallbackNode);
  }

  // Schedule a new callback.
  let newCallbackNode;
  // Synchronization priority
  if (newCallbackPriority === SyncLane) {
    // Place performSyncWorkOnRoot in the sync execution callback queue SyncCallbacks
    if (root.tag === LegacyRoot) {
      // In Legacy mode, schedule rendering performSyncWorkOnRoot
      scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
    } else {
      // In concurrent mode, schedule rendering performSyncWorkOnRoot
      scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    }
    // Whether microtasks are supported
    // The current synchronization priority is performed immediately
    if (supportsMicrotasks) {
      FlushSyncCallbacks: immediately execute performSyncWorkOnRoot
      scheduleMicrotask(flushSyncCallbacks);
    } else {
      FlushSyncCallbacks: immediately execute performSyncWorkOnRoot
      scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
    }
    newCallbackNode = null;
  } else {
    // Asynchronous priority in concurrent mode.
    let schedulerPriorityLevel;
    // lane => scheduler Priority
    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }
    // Saves tasks created by the Scheduler
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }
  // Save, schedule priority
  root.callbackPriority = newCallbackPriority;
   // Saves tasks created by the Scheduler
  root.callbackNode = newCallbackNode;
}
Copy the code

performSyncWorkOnRoot

This function will diff and render the DOM. This article does not go into details about how to render.

review

Writing time: 2021-01-16 React Version: 17.0.3