Here we go. Touch and write a hook

Hooks: 👍, 🐶. Hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: 👍, 🐶

Step 1: Introduce React and ReactDOM

Since we’re going to convert JSX to virtual-dom, we’ll leave that to Babel, and JSX will be lexically parsed by Babel to form a call to react.createElement (). The react.createElement () result is a JSX object or virtual-dom.

Since we’re rendering our demo to the DOM, we’ll introduce the ReactDOM.

import React from "react";
import ReactDOM from "react-dom";
Copy the code

Step 2: Let’s write a small demo

We define two states, count and age, that trigger an update when clicked, increasing their value by one.

In the source code useState is saved on a Dispatcher object, and mount and update get different hooks, so let’s get useState from Dispatcher for now and define Dispatcher later.

Next, define a schedule function that rerenders the component each time it is called.

function App() {
  let [count, setCount] = Dispatcher.useState(1);
  let [age, setAge] = Dispatcher.useState(10);
  return (
    <>
      <p>Clicked {count} times</p>
      <button onClick={()= > setCount(() => count + 1)}> Add count</button>
      <p>Age is {age}</p>
      <button onClick={()= > setAge(() => age + 1)}> Add age</button>
    </>
  );
}

function schedule() {	// Each call rerenders the component
  ReactDOM.render(<App />.document.querySelector("#root"));
}

schedule();
Copy the code

Step 3: Define Dispatcher

Before looking at this part, let’s clarify the relationship between Fiber, Hook and Update.

“Dispatcher” is an object that contains a number of hooks that need to be fixed from mount to update.

After calling useState, a resolveDispatcher function is called, which returns a Dispatcher object with hooks such as useState.

So let’s see what this function does, it’s a simple function, it gets current directly from the ReactCurrentDispatcher object, and it returns current as dispatcher, What is ReactCurrentDispatcher? Don’t worry, continue to look in the source code.

There is such a code in the source code, if in the formal environment, divided into two cases

  1. If meetcurrent === null || current.memoizedState === nullThat means we’re in the first render, which ismountOf which timecurrentIs ourfiberNode,memoizedStateSave thefiberonhookWhich means that when you apply the first render,current fiberIt doesn’t exist. We haven’t created anyfiberNodes, or there are somefiber, but the corresponding one is not built abovehookAt this point, we can think of it as being in the first render, and what we get isHooksDispatcherOnMount
  2. If notcurrent === null || current.memoizedState === null, means that we are in the renewal phase, i.eupdateWhen we get, what we get isHooksDispatcherOnUpdate
if (__DEV__) {
    if(current ! = =null&& current.memoizedState ! = =null) {
      ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
    } else if(hookTypesDev ! = =null) {
      ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
    } else{ ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV; }}else {
    ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  }
Copy the code

HooksDispatcherOnMount and HooksDispatcherOnUpdate are hooks.

const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useOpaqueIdentifier: mountOpaqueIdentifier,

  unstable_isNewReconciler: enableNewReconciler,
};

const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,

  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
  useMutableSource: updateMutableSource,
  useOpaqueIdentifier: updateOpaqueIdentifier,

  unstable_isNewReconciler: enableNewReconciler,
};
Copy the code

So the dispatcher is an object that contains all the hooks, which get different dispatchers for the first rendering and update, which call different functions, for example, if you use useState, Mount is called mountState and update is called updateState.

Now let’s write the dispatcher by hand. The Dispatcher is an object that has useState on it, and it’s represented by a self-executing function. In addition, we need to use two variables and a constant fiber

  • workInProgressHookRepresents the traversalhook(because thehookWill be saved on the linked list, need to walk through the list calculationhookState saved on)
  • For simplicity, let’s define oneisMount=truesaidmountAt the time ofupdateSet it tofalse.
  • For simplicity,fiberIs defined as an object,memoizedStateSaid thisfiberStored on the nodehookList,stateNodeThat’s the demo from step 2.
let workInProgressHook;// Current working hook
let isMount = true;// Whether to mount time

const fiber = {/ / fiber node
  memoizedState: null./ / hook chain table
  stateNode: App
};

const Dispatcher = (() = > {/ / the Dispatcher object
  function useState(){
    / /...
  }

  return{ useState }; }) ();Copy the code

Before defining useState, let’s first look at the data structures for hook and update

Hook:
  • queue: it haspendingProperties,pendingIt’s also a circular list of unupdated itemsupdateThat is to say, theseupdateinnextThe Pointers are joined into a circular list.
  • memoizedStateIndicates the current status
  • next: Point to the next onehookForm a linked list
 const hook = {/ / build the hooks
   queue: {
     pending: null// Update list not executed
   },
   memoizedState: null./ / the current state
   next: null// Next hook
 };
Copy the code
Update:
  • action: is the function that starts the update
  • next: Connect to the next oneupdateForm a circular list
 const update = {/ / build the update
    action,
    next: null
  };
Copy the code

Define useState in three parts:

  • createhookOr take tohook:
    1. inmountWhen: callmountWorkInProgressHookCreate an initialhook, assignmentuseStateThe initial value passed ininitialState
    2. inupdateWhen: callupdateWorkInProgressHook, get the current workinghook
  • To calculatehookUnupdated state on: traversalhookOn thependingLinked list, calls on the linked list nodeactionFunction to generate a new state and then update ithookThe state of.
  • Returns the new state anddispatchActionThe incomingqueueparameter
function useState(initialState) {
  	// Step 1: create hook or fetch hook
    let hook;
    if (isMount) {
      hook = mountWorkInProgressHook();
      hook.memoizedState = initialState;// Initial state
    } else {
      hook = updateWorkInProgressHook();
    }
		// Step 2: Calculate the unupdated state on the hook
    let baseState = hook.memoizedState;// Initial state
    if (hook.queue.pending) {
      let firstUpdate = hook.queue.pending.next;// The first update

      do {
        const action = firstUpdate.action;
        baseState = action(baseState);// Call action to calculate the new state
        firstUpdate = firstUpdate.next;// Calculate state using the action of update
      } while(firstUpdate ! == hook.queue.pending);// Loop through the list while it is still running

      hook.queue.pending = null;// Reset the update list
    }
    hook.memoizedState = baseState;// Assign a new state
  
		// Step 3: Return the new status and the dispatchAction incoming queue parameter
    return [baseState, dispatchAction.bind(null, hook.queue)];/ / useState returns
  }
Copy the code

Next, define the mountWorkInProgressHook and updateWorkInProgressHook functions

  • mountWorkInProgressHookIn:mountCreate a new onehookObject,
    1. If the currentfiberThere is nomemoizedStateThe currenthookThis is thefiberThe first one onhookThat will behookAssigned tofiber.memoizedState
    2. If the currentfiberThere arememoizedState, that will be currenthookIn theworkInProgressHook.nextbehind
    3. The currenthookAssigned toworkInProgressHook
  • updateWorkInProgressHookIn:updateReturns the currenthook, that is,workInProgressHookAnd will beworkInProgressHookPoint to thehookNext on the list.
function mountWorkInProgressHook() {/ / call when the mount
    const hook = {/ / build the hooks
      queue: {
        pending: null// Update list not executed
      },
      memoizedState: null./ / the current state
      next: null// Next hook
    };
    if(! fiber.memoizedState) { fiber.memoizedState = hook;// The first hook is directly assigned to Fibre. memoizedState
    } else {
      workInProgressHook.next = hook;// If it is not the first hook, add it after the previous hook to form a linked list
    }
    workInProgressHook = hook;// Record the current working hook
    return workInProgressHook;
  }

function updateWorkInProgressHook() {/ / update call
  let curHook = workInProgressHook;
  workInProgressHook = workInProgressHook.next;// Next hook
  return curHook;
}
Copy the code

Step 4: Define dispatchAction

  • Create update and mount queue. Pending

    1. If beforequeue.pendingIt doesn’t exist. So this one that was createdupdateThat’s the first oneupdate.next = update
    2. If beforequeue.pendingExists, this will be createdupdatejoinqueue.pendingIn a circular list

  • Set isMount=false, assign workInProgressHook, call Schedule to update render

function dispatchAction(queue, action) {// Trigger the update
  const update = {/ / build the update
    action,
    next: null
  };
  if (queue.pending === null) {
    update.next = update;// Update the circular list
  } else {
    update.next = queue.pending.next;// The next of the new update points to the previous update
    queue.pending.next = update;// Next of the previous update points to the new update
  }
  queue.pending = update;/ / update the queue pending

  isMount = false;// mark the end of mount
  workInProgressHook = fiber.memoizedState;/ / update the workInProgressHook
  schedule();// Schedule updates
}
Copy the code

The final code

import React from "react";
import ReactDOM from "react-dom";

let workInProgressHook;// Current working hook
let isMount = true;// Whether to mount time

const fiber = {/ / fiber node
  memoizedState: null./ / hook chain table
  stateNode: App//dom
};

const Dispatcher = (() = > {/ / the Dispatcher object
  function mountWorkInProgressHook() {/ / call when the mount
    const hook = {/ / build the hooks
      queue: {
        pending: null// Update list not executed
      },
      memoizedState: null./ / the current state
      next: null// Next hook
    };
    if(! fiber.memoizedState) { fiber.memoizedState = hook;// The first hook is directly assigned to Fibre. memoizedState
    } else {
      workInProgressHook.next = hook;// If it is not the first hook, add it after the previous hook to form a linked list
    }
    workInProgressHook = hook;// Record the current working hook
    return workInProgressHook;
  }
  function updateWorkInProgressHook() {/ / update call
    let curHook = workInProgressHook;
    workInProgressHook = workInProgressHook.next;// Next hook
    return curHook;
  }
  function useState(initialState) {
    let hook;
    if (isMount) {
      hook = mountWorkInProgressHook();
      hook.memoizedState = initialState;// Initial state
    } else {
      hook = updateWorkInProgressHook();
    }

    let baseState = hook.memoizedState;// Initial state
    if (hook.queue.pending) {
      let firstUpdate = hook.queue.pending.next;// The first update

      do {
        const action = firstUpdate.action;
        baseState = action(baseState);
        firstUpdate = firstUpdate.next;// Loop through the update list
      } while(firstUpdate ! == hook.queue.pending);// Calculate state using the action of update

      hook.queue.pending = null;// Reset the update list
    }
    hook.memoizedState = baseState;// Assign a new state

    return [baseState, dispatchAction.bind(null, hook.queue)];/ / useState returns
  }

  return{ useState }; }) ();function dispatchAction(queue, action) {// Trigger the update
  const update = {/ / build the update
    action,
    next: null
  };
  if (queue.pending === null) {
    update.next = update;// Update the circular list
  } else {
    update.next = queue.pending.next;// The next of the new update points to the previous update
    queue.pending.next = update;// Next of the previous update points to the new update
  }
  queue.pending = update;/ / update the queue pending

  isMount = false;// mark the end of mount
  workInProgressHook = fiber.memoizedState;/ / update the workInProgressHook
  schedule();// Schedule updates
}

function App() {
  let [count, setCount] = Dispatcher.useState(1);
  let [age, setAge] = Dispatcher.useState(10);
  return (
    <>
      <p>Clicked {count} times</p>
      <button onClick={()= > setCount(() => count + 1)}> Add count</button>
      <p>Age is {age}</p>
      <button onClick={()= > setAge(() => age + 1)}> Add age</button>
    </>
  );
}

function schedule() {
  ReactDOM.render(<App />.document.querySelector("#root"));
}

schedule();
Copy the code

IO /s/custom-ho…

Video explanation (efficient learning) :Click on the learning

React source code

1. Introduction and questions

2. React design philosophy

React source code architecture

4. Source directory structure and debugging

5. JSX & core API

Legacy and Concurrent mode entry functions

7. Fiber architecture

8. Render phase

9. The diff algorithm

10. com MIT stage

11. Life cycle

12. Status update process

13. Hooks the source code

14. Handwritten hooks

15.scheduler&Lane

16. Concurrent mode

17.context

18 Event System

19. Write the React mini

20. Summary & Answers to interview questions in Chapter 1