There are three articles in this series:

Quick question before class

  1. How do hooks store states
  2. How do you differentiate react when there are multiple hooks of the same type

define

The React hooks API is defined in the React library. Let’s use useState as an example:

export function useState<S> (initialState: (() = >S) | S) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}
Copy the code

We can see that the definition of hooks is very simple, just take dispatch and then call the useState property corresponding to the dispatcher, other hooks are similar, For example, useEffect is the useEffect property that calls dispatcher.

Then we need to look at what the dispatcher, by looking at the resolveDispatcher. We found that the dispatcher point to an ReactCurrentDispatcher current.

function resolveDispatcher() {
  constdispatcher = ReactCurrentDispatcher.current; invariant( dispatcher ! = =null.'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
    ' one of the following reasons:\n' +
    '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
    '2. You might be breaking the Rules of Hooks\n' +
    '3. You might have more than one copy of React in the same app\n' +
    'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',);return dispatcher;
}
Copy the code

We found by global searching ReactCurrentDispatcher. Current in ReactFiberHooks. Js assigned in this file, then we take a look at this file.

renderWithHooks

preface

After searching we found ReactCurrentDispatcher. Current in ReactFiberHooks. Js files are frequently assignment, one of the main assigned place in renderWithHooks approach, After a search, I found that renderWithHooks are called several times in reactFiberBeginwork.js. If you’ve seen the previous document or are familiar with the source code of the React update process, You should know that the reactFiberBeginwork.js file corresponds to the beginWork method, which finds the fiber object to be updated and executes the corresponding update method. After searching, I found several methods related to function Component: UpdateFunctionComponent and mountIndeterminateComponent, both are updated the function component, Difference is that the first rendering is called mountIndeterminateComponent, because for the first time also is unable to determine the function component or class component.

mountIndeterminateComponent:

updateFunctionComponent:

Let’s take a look at what renderWithHooks do.

The flow chart

Online address: www.processon.com/view/link/5…

The specific logic

From the flowchart above, we see that renderWithHooks do the following:

  1. NextCurrentHook is null to determine if it is the first render, If the first is rendering will ReactCurrentDispatcher. Current assignment for HooksDispatcherOnMount assignment for HooksDispatcherOnUpdate otherwise
  2. Then call function Component to get children
  3. Determine whether there is a nested update (didScheduleRenderPhaseUpdate), if there is a will continue to perform the second step, until the end of the nested update or more than the biggest update nested layers
  4. Set memoizedState on the current fiber object to the current hook object, as well as other properties, and mark the effectTag to a sideEffectTag
  5. Resetting global variables
  6. Return to the children

HooksDispatcherOnMount

Introduction to the

The HooksDispatcherOnMount object defines the implementation of the various hooks API in the initial render

The flow chart

www.processon.com/view/link/5…

HooksDispatcherOnUpdate

Introduction to the

The HooksDispatcherOnUpdate object defines the implementation of the various hooks API in re-render

The flow chart

Online address: www.processon.com/view/link/5…

useState

After the previous story at this point you should know useState final call is ReactCurrentDispatcher current. UseState and ReactCurrentDispatcher. Current is in RenderWithHooks are assigned HooksDispatcherOnMount or HooksDispatcherOnUpdate, so let’s first look at HooksDispatcherOnMount.

mountState

  1. We first create a hook object by calling the mountWorkInProgressHook method
  2. Determine if the passed initialState (useState) parameter is a function, and if it is, execute it to get the initialState
  3. MemoizedState and hook. BaseState are initialState. Here you can see why function Component can save state when hook is used. Because the state is stored on the hook object, and the hook object is stored on the memoizedState property of the fiber object
  4. Create a queue object and assign it to hook.queue. Queue is similar to updateQueue on the fiber object
  5. To bind the current fiber (workInProgress) and queue to the first two parameters of dispatchAction and assign values to Dispatch
  6. return[hook.memoizedState, dispatch]

updateState

UpdateState internally calls updateReducer, and updateRecucer internally does the following:

  1. We first create a hook object by calling the mountWorkInProgressHook method
  2. The assignmentqueue.lastRenderedReducerFor basicStateReducer
  3. If there is a repeat render, we go to renderPhaseUpdates and get the update from the queue and iterate through the update list to get the newState, It then determines whether newState and oldState are equal, if not, it marks the update, and finally returns [newState, dispatch].
  4. If there is no repeat render, find the last update from queue, and then find the first udpate. Because it is a circular list, you can find first by last.next, and then iterate through the update list as in step 4 to get newState. It then determines whether newState and oldState are equal, if not, it marks the update, and finally returns [newState, dispatch].

dispatchAction

DispatchAction is the second parameter returned by useState

The flow chart

Online address: www.processon.com/view/link/5…

The specific logic

  1. Render first determines whether it is in a phase of the update, if it is didScheduleRenderPhaseUpdate set to true, the sign bit is used to determine whether in a nested in renderWithHooks updates, and then create an update object, Create a renderPhaseUpdates Map object and store it in renderPhaseUpdate with queue as key update and value as value. RenderPhaseUpdate is called in the updateState method
  2. If you do not have an update in the render phase, you would like to add an update object to your queue. You would like to add an update object to your queue. Then decide if the current fiber. ExpirationTime = NoWork, and queue. LastRenderedReducer is not empty, We can calculate the new state (eagerState) by using lastRenderedReducer, LastRenderedReducer accepts the previous state (currentState) and action (which are passed to the second method returned by useState), Next, assign lastRenderedReducer and eagerState to the eagerReducer and eagerState of the update. If = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = So this links up to the process that we talked about in the last video.

Questions addressed in this chapter

  1. How do hooks store states

UseEffect

Now you should know that useEffect is just like useState, Final call is ReactCurrentDispatcher. Current. UseEffect and ReactCurrentDispatcher. Current is assigned a value of in renderWithHooks HooksDispatcherOnMount or HooksDispatcherOnUpdate, so let’s first look at the implementation of HooksDispatcherOnMount.


mountEffect

HooksDispatcherOnMount useEffect points to mountEffect, which in turn calls mountEffectImpl

function mountEffect(
  create: () = > (() = >void) | void.deps: Array<mixed> | void | null,
): void {
  return mountEffectImpl(
    UpdateEffect | PassiveEffect,
    UnmountPassive | MountPassive,
    create,
    deps,
  );
}
Copy the code

mountEffectImpl

MountEffectImpl does the following:

  1. Create a hook object from mountWorkInProgressHook
  2. Set the incoming fiberEffectTag onto sideEffectTag, corresponding to the mountEffect is UpdateEffect | PassiveEffect, Eventually the sideEffectTag will be set to the current fiber object’s effectTag (see renderWithHooks).
  3. The last call pushEffect, incoming hookEffectTag (UnmountPassive | MountPassive), create, nextDeps
  4. Assign the result of pushEffect to hook. MemoizedState

updateEffect

HooksDispatcherOnUpdate, HooksDispatcherOnUpdate, useEffect is updateEffect, It calls updateEffectImpl again.

function updateEffect(
  create: () = > (() = >void) | void.deps: Array<mixed> | void | null,
): void {
  return updateEffectImpl(
    UpdateEffect | PassiveEffect,
    UnmountPassive | MountPassive,
    create,
    deps,
  );
}
Copy the code

updateEffectImpl

UpdateEffectImpl does the following:

  1. Create a hook object from updateWorkInProgressHook
  2. Whether currentHook is null, currentHook not null is not the first time rendering, obtain currentHook. MemoizedState, which is an effect on the object, Find the deStory and deps properties of the object and determine if the new deps and the old deps are equal. If they are equal, call pushEffect and pass NoHookEffect to indicate that there is no effect to perform. Instead of unmount and mount during the COMMIT phase, you call destroy and create and then return
  3. If currentHook equal to null or new deps and old deps are not equal, the incoming fiberEffectTag set on sideEffectTag (UpdateEffect | PassiveEffect), Eventually the sideEffectTag is set to the current fiber object’s effectTag (see renderWithHooks), and finally the pushEffect is called, The incoming hookEffectTag (UnmountPassive | MountPassive), create, nextDeps, assigned to the results of pushEffect hook. MemoizedState

pushEffect

  1. Create an effect object
  2. Add effect to the componentUpdateQueue. LastEffect, forming a circular linked list, ComponentUpdateQueue will be added to the updateQueue of the current fiber object (see renderWithHooks)
  3. Returns the effect

effect

const effect: Effect = {
    tag, // hookEffectTag
    create, // useEffect is the first parameter received
    destroy, // mountEffect is undefined
    deps, // useEffect accepts the second argument
    // Circular
    next: (null: any), // Point to the next effect
  };
Copy the code

commitLayoutEffects

The resulting updateQueue is executed in commitLayoutEffects during the commit phase. See the next article for details

CommitLayoutEffectOnFiber (commitLifeCycles)

Will remember mountEffectImpl method above UpdateEffect | PassiveEffect set to fiber. The effectTag, For a fiber with UpdateEffect object will perform in commitLayoutEffects commitLayoutEffectOnFiber method, it is the corresponding commitLifeCycles method, In this method, the FunctionComponent executes the commitHookEffectList method, passing in UnmountLayout, MountLayout, and finishedWork

commitHookEffectList

This method unmount and mount the effect object on the finishedWork.updatequeue passed in. This method calls the destroy and create methods on the effect object. Corresponding to the method returned by useEffect and the method passed in, the first render sets destroy to undefined so the first render destroy will not be executed

useRef

UseRef and other hooks as final call is ReactCurrentDispatcher current. UseRef and ReactCurrentDispatcher. The current is in renderWithHooks HooksDispatcherOnMount or HooksDispatcherOnUpdate, so let’s take a look at HooksDispatcherOnMount.


mountRef

In HooksDispatcherOnMount, useRef points to the mountRef method. Let’s see what it does:

  1. The hook object is created using the mountWorkInProgressHook method
  2. Create the ref objectconst ref = { current: initialValue };The initial value is the first argument passed to useRef
  3. Set up thehook.memoizedState = ref;
  4. Returns the ref

updateRef

In HooksDispatcherOnUpdate, useRef points to the updateRef method. Let’s see what it does:

  1. Hook obtained from updateWorkInProgressHook
  2. Returns memoizedState on the hook object

Create a hook

After the implementation of the above hook API, we found that each hook API needs to create a hook object first, and the methods used to create the hook object are different for the first rendering and the second rendering. Let’s look at the first rendering first.

mountWorkInProgressHook

The first render calls the mountWorkInProgressHook method. Let’s see what it does:

  1. Create a hook object
  2. Determine if the workInProgressHook is null, and if so, point the workInProgressHook and firstWorkInProgressHook to the new hook
  3. Insert after (next) if not empty, and then point workInProgress to the new hook
  4. Return workInProgress

updateWorkInProgressHook

Next, let’s look at the updateWorkInProgressHook method called when we render again:

  1. First determines whether the nextWorkInProgressHook is empty, if not is empty that triggered the current in the rendering stage to apply colours to a drawing, because only in to render renderWithHooks will set it to firstWorkInProgressHook, If null then set workInProgressHook to nextWorkInProgressHook, then set nextWorkInProgressHook to workInProgreshook.next, Then set nextCurrentHook
  2. If nextWorkInProgressHook is empty, set currentHook to nextCurrentHook, that is, find the last rendered hook object (similar to current in fiber). Then copy a newHook from currentHook, execute the second and third steps on mountWorkInProgressHook, and then point nextCurrentHook to next on currentHook. Here we can know why react corresponds one to one when multiple hook apis are executed, that is, through the linked list formed by the first rendering, so be sure to pay attention to the order of hooks should not be changed in the two renderings
  3. Return workInProgress

hook

Let’s see what a hook object is

const hook: Hook = {
    memoizedState: null.baseState: null.queue: null.baseUpdate: null.next: null};Copy the code

memoizedState

UseState is the state object, useEffect is the effect object, useRef is the ref object

baseState

Related to useState, is equal to the initial state passed in on the first render, followed by each new state computed

queue

Similar to the fiber object’s updateQueue, each call to the setSomeState method returned by useState creates an update object and puts it in the queue, which is then traversed in the Render phase to calculate the new state

const queue = (hook.queue = {
    last: null.// Points to the last update, and its next points to the first update. This is a circular list
    dispatch: null.// Dispatch method to calculate the new state
    lastRenderedReducer: reducer, // The reducer of the last update
    lastRenderedState: (initialState: any), // Point to the state generated by the last update
  });
Copy the code

This section addresses the problem

  1. How do you differentiate react when there are multiple hooks of the same type

Github

Includes annotated source code, demos, and flowcharts github.com/kwzm/learn-…