preface

UseState,useState, and useReducer, which are fixed from the hooks of react. Use the same code as useState, which are fixed from the hooks of React. So we just need to first understand the useState,useReduecr that is no words, next together with today’s learning!

The preparatory work

First, let’s look at the next problem

const Increaser = () => {
  const [count, setCount] = useState(0);
  
  const increase = useCallback(() => {
    setCount(count + 1);
  }, [count]);
  
  const handleClick = () => {
    increase();
    increase();
    increase();
  };
  
  return (
    <>
      <button onClick={handleClick}>+</button>
      <div>Counter: {count}</div>
    </>
  );
}
Copy the code

When we perform handleClick, we see that the count only increases by one, so why? This is because on the first setCount count changes to 1, but on the second and third setCount returns the old state (count is 0), so count is 1. This is because the state variable is updated this time. The solution is to update the status of the function as a function:

const Increaser = () => {
  const [count, setCount] = useState(0);
  
  const increase = useCallback(() => {
    setCount(count => count + 1);
  }, [count]);
  
  const handleClick = () => {
    increase();
    increase();
    increase();
  };
  
  return (
    <>
      <button onClick={handleClick}>+</button>
      <div>Counter: {count}</div>
    </>
  );
}
Copy the code

Can see from this problem, as for the answer to the question in the following, if we don’t know some hooks run mechanism, we are likely to have encountered some unknown error, such as the closure problem, we all know the React hooks are implemented based on the closure, a scenario is we found that we clearly have updated the status, But it keeps getting the status, which is all the more reason to look at some hooks source code.

Why are hooks there

Everyone knows that hooks are the product of function components. Why didn’t hooks like that appear in the class component earlier?

The simple answer is no.

Because in a Class component, at runtime, only one instance is generated, and information such as the component’s state is stored in that instance. In subsequent update operations, only the render method is called, and the information in the instance is not lost. In a function component, every render and update will execute the function component, so there is no way to store state or other information in the function component. To keep information like state, there are hooks that record the state of function components and perform side effects

The hooks record the state in the Fiber node. The hooks record the state in the fiber node.

Two sets of hooks

When we first learned React, we might have wondered, why can’t we write hooks in functions or conditional statements? This is because React maintains two sets of hooks, which are fixed on mount and fixed on update. In many cases it is necessary to keep in mind that mount and update are doing different things.

Hooks storage

Each initialized hook creates a hook structure, and multiple hooks are linked to each other in a linked list declared in order. Finally, this list is stored in Fiber. memoizedState:

MemoizedState: null baseState: null baseQueue: null baseQueue: null, queue: Next: null // Link the next hook};Copy the code

Initialize the mount

Let’s start with the useState() function:

function useState(initialState) {
  var dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}
Copy the code

InitialState is the parameter that we pass in to useState. It can be a base datatype or a function. We are mainly looking at the dispatcher.usestate (initialState) method, because we are initializing it and it calls the mountState method:

function mountState<S>( initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] {// Create and return the current hook const hook = mountWorkInProgressHook(); / /... Const queue = (hook. Queue = {pending: null, dispatch: null, lastRenderedReducer: basicStateReducer, lastRenderedState: (initialState: any), }); / /... Return [hook.memoizedState, dispatch]; }Copy the code

The above code is still relatively simple, generating a queue from the useState() input and storing it in the hook, then exposing the input and the dispatchAction bound with two parameters as return values to the function component.

The two return values, the first hook. MemoizedState, which is the initial value, the second dispatch, So dispatchAction.bind(null, currentlyRenderingFiber$1, queue) what is that? We know that using useState() returns two values, setState, which corresponds to dispatchAction above. How does this function setState for us?

We’ll save that question for now, and we’ll find out the answer later on.

Next, let’s look at what the MountWork in Progress Shook did.

mountWorkInProgressHook

function mountWorkInProgressHook() { var hook = { memoizedState: null, baseState: null, baseQueue: null, queue: null, next: null }; If (workInProgressHook === null) {currentlyRenderingFiber$1. MemoizedState = workInProgressHook = hook; } else {// add hook to bottom of hook list, and pointer to hook workInProgreshook. next = hook; } return workInProgressHook; }Copy the code

CurrentlyRenderingFiber $1 from above. MemoizedState = workInProgressHook = hook; In this line of code, we can see that the hook is stored on the corresponding Fiber. MemoizedState.

workInProgressHook = workInProgressHook.next = hook; From this line of code, we can know that if there are multiple hooks, they are stored in the form of a linked list.

Not only does useState() call the mountWorkInProgressHook method when initialized, but other hooks such as useEffect, useRef, useCallback, etc.

Here we can figure out two things:

  • hooksThe state data is stored in the corresponding function componentfiber.memoizedState;
  • If there are more than one function componenthookThey will passOrder of declarationStored in a linked list structure;

At this point, our useState() has done all of its initialization. To recap, useState() stores the initialization values we pass in as hooks into the corresponding fiber.memoizedState, Returns [state, dispatchAction] as an array.

Update the update

When setState() is triggered in some way, React also determines how to update the view based on the value of setState().

UseState returns [state, dispatchAction] when initialized, so we call setState(), which actually calls dispatchAction, and this function initializes with two parameters called bind. One is fiber for useState initialization and the other is queue for hook structure.

Let’s take a look at my streamlined dispatchAction (remove the code that has nothing to do with setState)

Function dispatchAction(fiber, queue, action) {function dispatchAction(fiber, queue, action) { lane, action: action, eagerReducer: null, eagerState: null, next: null }; // Insert update into the closed loop list. var pending = queue.pending; if (pending === null) { update.next = update; } else { update.next = pending.next; pending.next = update; } queue.pending = update; var alternate = fiber.alternate; / / to determine whether the current render phase the if (fiber. The lanes = = = NoLanes && (alternate = = = null | | alternate. Lanes = = = NoLanes)) {var lastRenderedReducer = queue.lastRenderedReducer; If (lastRenderedReducer!) we have the same update as last time. If (lastRenderedReducer! == null) { var prevDispatcher; { prevDispatcher = ReactCurrentDispatcher$1.current; ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; } try { var currentState = queue.lastRenderedState; var eagerState = lastRenderedReducer(currentState, action); update.eagerReducer = lastRenderedReducer; update.eagerState = eagerState; if (objectIs(eagerState, currentState)) { return; } } finally { { ReactCurrentDispatcher$1.current = prevDispatcher; }}}} // scheduleUpdateOnFiber(Fiber, Lane, eventTime); }}Copy the code

To summarize what dispatchAction does:

  • To create aupdateTo join thefiber.hook.queueIn the list, and the list pointer points to thisupdate;
  • Determine if it is currently in the render phase and decide whether to schedule updates immediately;
  • Check whether the operation this time is the same as the operation last time. If the operation is the same, no scheduling update is performed.
  • Meet the above conditions will be withupdatethefiberScheduling updates;

Here’s another thing we’ve figured out:

whysetStateIs the function component not updated at the same value?

updateState

We will not go into the details of the update scheduling process, but we need to know that during the next update process, our function component will be executed again and the useState method will be called again. As mentioned earlier, React maintains two sets of hooks, one for initialization and one for updates. This is already done when scheduling updates. So we’ll call the useState method differently this time than we did before.

This time when we go to useState, we’ll see that we’re actually calling the updateState method

function updateState(initialState) { return updateReducer(basicStateReducer); } Duplicate codeCopy the code

UseState is similar to useReducer. The useState update educer is called in the useState update.

updateReducer

Function reducereducer (reducer, initialArg, init) { Update var hook = updateWorkInProgressHook(); var queue = hook.queue; queue.lastRenderedReducer = reducer; var current = currentHook; var baseQueue = current.baseQueue; var pendingQueue = queue.pending; current.baseQueue = baseQueue = pendingQueue; if (baseQueue ! == null) {// Create a closed list and insert an update. Update var first = basequeue.next; var newState = current.baseState; var update = first; SetState do {var updateLane = update.lane; Var action = update.action; var action = update.action; The reducer will determine the action type. NewState = Reducer (newState, action); update = update.next; } while (update ! == null && update ! == first); hook.memoizedState = newState; hook.baseState = newBaseState; hook.baseQueue = newBaseQueueLast; queue.lastRenderedState = newState; } var dispatch = queue.dispatch; return [hook.memoizedState, dispatch]; }Copy the code

In the update above, a merge operation is performed over the update loop, and only the value of the last setState is taken.

This is not possible because the setState input can be either a base type or a function, which explains the reducer example at the beginning of this article. If a function is passed in, it relies on the value of the previous setState to complete the update

function basicStateReducer(state, action) {
  return typeof action === 'function' ? action(state) : action;
}
Copy the code

So here we have a question, how do multiple set states merge?

updateWorkInProgressHook

The following is the pseudo-code, and some logic has been deleted. In the original code, it will determine whether the current hook is the first hook to schedule update. I will parse it according to the first hook for simplicity

function updateWorkInProgressHook() {
  var nextCurrentHook;

  nextCurrentHook = current.memoizedState;

  var newHook = {
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null
      }
      
  currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook;

  return workInProgressHook;
}
Copy the code

As you can see from the code above, the updateWorkInProgressHook removes those judgments and actually does a very simple job of creating a new hook structure based on fiber. MemoizedState that overwrites the previous hook. The dispatchAction will add the update to the hook. Queue, which is on newhook. queue.

conclusion

This is the fourth article in this series and changed my typographic style, looks beautiful, about useState, should be developed in one of the most commonly used hooks, learn some basic principles to avoid mistakes in the development, of course a lot wrong or have experience in development, we got the old state, for example, the closure problem, Hooks such as useEffect will be updated next week. Recently, I spent most of my time on webpack optimization, so there has been no update for a while. I hope to write an article about Webpack sometime.

React Source code

  • React (1) — Fiber
  • React source code (2) — Render phase
  • React source code (3) — Commit phase
  • React source code read (4) — hooks useState,useReducer
  • React source code read (5) — useEffect of hooks