Reactdom.render reactDom.render reactDom.render ReactDom.render ReactDom.render ReactDom.render

The process is briefly

The function component calls the renderWithHooks function, which flags the currentlyRenderingFiber node that is currently being rendered, And decide which one to use HooksDispatcher (React in the Mount, and Update the Hooks used is not the same), then perform this function components, processing hook function respectively, and the children, the children to return to the upper function, Execute the reconcileChildren to generate child nodes.

renderWithHooks

The renderWithHooks function is the entry function to the function component, Whether the Mount mountIndeterminateComponent or when the Update updateFunctionComponent will enter this function to get the function component of the children.

export function renderWithHooks<Props.SecondArg> (
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
) :any {
  // omit code...
  currentlyRenderingFiber = workInProgress;

  // Use the hook of mount or update
  ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount
      : HooksDispatcherOnUpdate;
  // omit code...
  
  // Children returned after executing the function
  let children = Component(props, secondArg);

  // omit code...
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;
  // omit code...
  
  return children;
}
Copy the code

CurrentlyRenderingFiber: {workInProgressFiber: {currentlyRenderingFiber: {}} Which ReactCurrentDispatcher to use and return children to the upper function.

Note that the hook used for Mount and Update is not the same.

const HooksDispatcherOnMount: Dispatcher = {
  // ...
  useReducer: mountReducer,
  useState: mountState,
};
const HooksDispatcherOnUpdate: Dispatcher = {
  // ...
  useReducer: updateReducer,
  useState: updateState,
};
Copy the code

Take a look at useState

export function useState<S> (
  initialState: (() => S) | S,
) :S.Dispatch<BasicStateAction<S> >]{
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

/ / returns the calling function component assignment before ReactCurrentDispatcher. Current
function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return dispatcher;
}
Copy the code

Therefore, the subsequent analysis will be divided into Mount and Update

Data structure for Hooks

The data structure of a single hook is as follows

type Hook = {|
  memoizedState: any, // The value for useState and useReducer is the value of state
  baseState: any,
  baseQueue: Update<any, any> | null.queue: UpdateQueue<any, any> | null.// Update the queue
  next: Hook | null.// Next hook node
|}
Copy the code

How does the function component find the corresponding hooks information? The memoizedState field in the corresponding Fiber node represents the first hook in the function component. The hooks data structure is a one-way list, and each node can use the next attribute to find the next hook.

function Container(){
  React.useState() / / currentlyRenderingFiber. MemoizedState (data structure is the Hook type)
  React.useState() // currentlyRenderingFiber.memoizedState.next (Hook.next)
 // ...
}
Copy the code

The update queue of each hook will be stored in the queue field, and the latest state value can be obtained by executing the operation in the queue. The structure is as follows

type UpdateQueue<S, A> = {|
  pending: Update<S, A> | null.// The latest update task
  dispatch: (A= > mixed) | null.// The second argument to useState is used to initiate the update
  lastRenderedReducer: ((S, A) = > S) | null.// Last reducer
  lastRenderedState: S | null.// Last state value
|};

type Update<S, A> = {|
  lane: Lane,
  action: A,
  eagerReducer: ((S, A) = > S) | null.eagerState: S | null.// Assign to dispatch the first time it is called
  next: Update<S, A>, // Next updatepriority? : ReactPriorityLevel, |};Copy the code

The update queue of a hook is a one-way circular list. The pending field stores the latest update. Update.

update2(pending) -> update0 -> update1 -> update2
Copy the code

UseState and useReducer

UseState is actually a useReducer syntax sugar with reducer, so it needs to be analyzed together.

mountState

function mountState<S> (
  initialState: (() => S) | S,
) :S.Dispatch<BasicStateAction<S> >]{
  // Create a new hook structure. If the workInProgressHook already exists, connect it with next
  const hook = mountWorkInProgressHook();
  // The initial value of useState
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  const queue = (hook.queue = {
    pending: null.dispatch: null.lastRenderedReducer: basicStateReducer, // Add a basicStateReducer
    lastRenderedState: (initialState: any),
  });
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}

UseState has its own reducer
function basicStateReducer<S> (state: S, action: BasicStateAction<S>) :S {
  return typeof action === 'function' ? action(state) : action;
}
Copy the code

MountState comes with a basicStateReducer

mountReducer

function mountReducer<S.I.A> (reducer: (S, A) => S, initialArg: I, init? : I => S,) :S.Dispatch<A>] {
  const hook = mountWorkInProgressHook();
  let initialState;
  // The initial value of useReducer
  if(init ! = =undefined) {
    initialState = init(initialArg);
  } else {
    initialState = ((initialArg: any): S);
  }
  hook.memoizedState = hook.baseState = initialState;
  const queue = (hook.queue = {
    pending: null.dispatch: null.lastRenderedReducer: reducer, // Incoming reducer
    lastRenderedState: (initialState: any),
  });
  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}
Copy the code

The difference between mountState and mountReducer is that the function parameters and initial values are assigned differently, but everything else is the same.

The input function first creates the hook object via the mountWorkInProgressHook, then initializes the queue, passes the Fiber node and queue to dispatchAction via bind, and finally returns the values.

mountWorkInProgressHook

The mountWorkInProgressHook function is simple. It creates a hook object and connects multiple hook objects into a one-way linked list.

function mountWorkInProgressHook() :Hook {
  // Create a new hook object
  const hook: Hook = {
    memoizedState: null.baseState: null.baseQueue: null.queue: null.next: null};if (workInProgressHook === null) {
    // If workInProgressHook is null, the current hook is the first hook in the function, and the memoizedState is assigned to the Fiber node
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Next hooks are connected via next
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}
Copy the code

dispatchAction

The dispatchAction method is used to create a new UPDATE to initiate the update

/* mountReducer ... const dispatch: Dispatch = (queue.dispatch = (dispatchAction.bind( null, currentlyRenderingFiber, queue, ): any)); * / 

// Bind fiber and queue before each call, just pass in the action
function dispatchAction<S.A> (fiber: Fiber, queue: UpdateQueue
       
        , action: A,
       ,>) {
  // omit code...
  
  const update: Update<S, A> = {
    lane,
    action,
    eagerReducer: null.eagerState: null.next: (null: any),
  };

  // Ring list
  // Add update to the end of the list
  const pending = queue.pending;
  if (pending === null) {
    // When pending is null, dispatchAction is called for the first time to initiate an update
    // Create a circular list
    update.next = update;
  } else {
    // Update to be added later
    update.next = pending.next;
    pending.next = update;
  }
  // Pending is the latest update
  queue.pending = update;

  const alternate = fiber.alternate;
  // Determine if this is an update to the Render phase
  if( fiber === currentlyRenderingFiber || (alternate ! = =null && alternate === currentlyRenderingFiber)
  ) {
    / / tag
    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  } else {
    // Determine whether this is the first update by priority (?)
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      // There is no update queue, dispatchAction is the first call to update, so state can be calculated before the Render phase
      const lastRenderedReducer = queue.lastRenderedReducer;
      if(lastRenderedReducer ! = =null) {
        try {
          const currentState: S = (queue.lastRenderedState: any);
          const eagerState = lastRenderedReducer(currentState, action);
          If there is no change, directly take the value of eagerState in the render stage
          update.eagerReducer = lastRenderedReducer;
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) {
            // If the result is consistent with the current state value, there is no need to initiate update scheduling. If not, the eagerState can be obtained in the Render phase to directly value the value without the need to calculate again
            return; }}catch (error) {
          // Errors are thrown during the render phase}}}// Initiate update schedulingscheduleUpdateOnFiber(fiber, lane, eventTime); }}Copy the code

The dispatchAction will connect the updates to the circular list. If it is not the render phase, the dispatchAction will determine whether there is an update on the Fiber node by priority. If there is no update, the dispatchAction will calculate the new state value and then determine whether the old and new values are the same. Same does not need to initiate update scheduling.

When dispatchAction is called multiple times synchronously multiple updates are generated and grouped into a circular list.

function Updater(){
  const render = useState(0) [1];
  return <div onClick={()= >{
      render(prev => prev+1);
      render(prev => prev+1);
      render(prev => prev+1);
    }}>update</div>
}
Copy the code

The circular list joins here are a little harder to understand

  const pending = queue.pending;
  if (pending === null) {
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
Copy the code

For the first time, pending === NULL creates a loop that connects to itself, denoted as u0 -> u0.

On the second execution, pending! == null, create a new update u1

  • update.next = pending.nextIs theu1.next = u0.next, the last time it was executedu0.next -> u0, the results foru1.next = u0
  • pending.next = updateAt this timependingisu0, that is, tou0.next = u1
  • queue.pending = updateIs thequeue.pending = u1When the final result isqueue.pending(u1) -> u0 -> u1

On the third execution, create a new update u2

  • update.next = pending.nextIs theu2.next = u1.next, the last time it was executedqueue.pending(u1) -> u0 -> u1, the results foru2.next = u0
  • pending.next = updateAt this timependingisu1, that is, tou1.next = u2
  • queue.pending = updateIs thequeue.pending = u2

Pending (u2) -> u0 -> U1 -> u2. Pending (u2) -> u1 -> u2. Pending (u2) -> u1 -> u2

The pending of the final ring-linked list always points to the latest update, and the latest update.next points to the first update, U0

updateState

UpdateState and updateReducer are exactly the same as the reducer that was passed in

function updateState<S> (
  initialState: (() => S) | S,
) :S.Dispatch<BasicStateAction<S> >]{
  return updateReducer(basicStateReducer, (initialState: any));
}
Copy the code

updateReducer

The value returned by mountReducer is initialState, and the value returned by updateReducer is the state calculated by calling the update in the queue.

function updateReducer<S.I.A> (reducer: (S, A) => S, initialArg: I, init? : I => S,) :S.Dispatch<A>] {
  // Update phase corresponding hook
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;

  queue.lastRenderedReducer = reducer;

  // currentHook corresponds to workInProgressHook
  const current: Hook = (currentHook: any);

  let baseQueue = current.baseQueue;

  const pendingQueue = queue.pending;
  if(pendingQueue ! = =null) {
    if(baseQueue ! = =null) {
      // omit code...
    }
    PendingQueue assigns the value to baseQueue
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }

  if(baseQueue ! = =null) {
    Pending is the latest update, and next is the first update
    const first = baseQueue.next;
    let newState = current.baseState;

    let newBaseState = null;
    let newBaseQueueFirst = null;
    let newBaseQueueLast = null;
    let update = first;
    do {
      const updateLane = update.lane;
      if(! isSubsetOfLanes(renderLanes, updateLane)) {// omit priority code...
      } else {
        if(newBaseQueueLast ! = =null) {
          // omit code...
        }

        EagerReducer is assigned only when dispatchAction is called for the first time to initiate an update
        // When the reducer has not changed
        if (update.eagerReducer === reducer) {
          newState = ((update.eagerState: any): S);
        } else {
          // The new state is not calculated the first time dispatchAction is called
          constaction = update.action; newState = reducer(newState, action); }}// Points to the next update
      update = update.next;
    } while(update ! = =null&& update ! == first);if (newBaseQueueLast === null) {
      newBaseState = newState;
    } else {
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }
    
    // The change is marked by a different value
    if(! is(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate(); }// Assign a new state
    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;

    queue.lastRenderedState = newState;
  }

  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}
Copy the code

The updateReducer will successively put the updates in the queue into the Reducer for calculation, and finally assign the new state value to Hook. memoizedState and return.

updateWorkInProgressHook

UpdateWorkInProgressHook and mountWorkInProgressHook function similar, return a shallow copy of the hook object, change the direction of the currentHook and workInProgressHook, Also join a new workInProgressHook chain table.

function updateWorkInProgressHook() :Hook {
  
  let nextCurrentHook: null | Hook;
  // The first time you enter the fiber node, execute hook, no hook pointing
  if (currentHook === null) {
    // Find the memoizedState hook object of the Alternate Fiber node
    const current = currentlyRenderingFiber.alternate;
    if(current ! = =null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null; }}else {
    // Alternate hook object for subsequent hook execution
    nextCurrentHook = currentHook.next;
  }

  let nextWorkInProgressHook: null | Hook;
  // omit code...

  if(nextWorkInProgressHook ! = =null) {
    // omit code...
  } else {
    currentHook = nextCurrentHook;

    // Shallow copy using currentHook
    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,

      next: null};// Assemble the workInProgressHook list
    if (workInProgressHook === null) {
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else{ workInProgressHook = workInProgressHook.next = newHook; }}return workInProgressHook;
}
Copy the code

conclusion

If the current WorkInProgressFiber node is Mount or Update, the current WorkInProgressFiber node is Mount or Update. If the current WorkInProgressFiber node is Mount or Update, the current WorkInProgressFiber node is Mount. Execute the function component to get the children itself.

MemoizedState: memoizedState: memoizedState: memoizedState: memoizedState: memoizedState: memoizedState: memoizedState: memoizedState UseState and useReducer store the state value itself. Queue represents the update queue of the current hook, which is a circular unidirectional linked list. Queue.pending refers to the latest update. Queue.pending. next performs the first update.

You can run mountState and mountReducer to obtain the initial state value, and run updateState and updateReducer to calculate the update in the queue to obtain the latest state value.

DispatchAction is called to initiate the update scheduling, and in dispatchAction, the updated queue circular unidirect-linked list will be assembled. Finally, in the Render phase, updateState and updateReducer will be executed to obtain the latest state value.

If there are mistakes, but also hope to exchange correction.