preface

Recently, I encountered the problem of React state management in my work. After checking some articles, I found that there were few explanations about the principle of useState. So this article goes from explaining principles to simple implementations and listing code examples. Hope to bring you some help, the future encountered state management problems can be easily solved.

Tip: When using React, knowing the react source code (which is more complex than the Vue source code) is not a requirement. This article mainly explores the inner workings of React, so it is suitable for students who are interested in the inner workings of React and those who are skilled in using Hook. I am a big advocate of using more hooks in your daily work to reduce code implementation or logical decoupling.

Class components are often difficult to understand and greatly increase our code volume. Using Hooks not only solves these two problems, it has other advantages, including:

  • Easy to build and reuse parts of state logic
  • It is easier to break complex components into smaller modules
  • Life cycle chaos is avoided
  • Easier to add state types
  • It’s easier to unit test your code

So it’s easy to ask, why are Hooks so powerful?

The principle of useState

Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class.

Take a look at an official example:

import React, { useState } from 'react';

function Example() {
  // Declare a state variable
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()= > setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Copy the code

Tip: Function components have no instances and no state. Its rendering is just a process of executing a function to get the return value and rendering it.

The useState component returns the current state and a method to update the state. The initial state is only assigned the first time the function is executed. Each time the state changes, the view is re-rendered. For example, if multiple USestates exist in Hooks, we can try to store those states in arrays.

Next, let’s try implementing a useState ourselves.

The realization of the useState

The preliminary implementation

// Put the state on the outside to record state changes
let state

const useState = initialValue= > {
  state = state || initialValue
  // Update the value of state
  const updateState = newValue= > {
    state = newValue
    render()
  }
  
  return [state, updateState]
}

/ / render method
const render = () = > {
  ReactDom.render(<App />.document.getElementById('root'))}Copy the code

Here we implement the storage and memory of a state. In practice, multiple states are often managed in daily development. So we came up with the idea that we could store all the states in an array.

Enhanced implementation

Now we need to optimize useState to solve the problem of not being able to manage multiple states at the same time (we don’t consider naming state variables the same from component to component).

// Store all state values
let stateArr = []
// Record index
let index = 0

const useState = initialValue= > {
  // Records the index of the current operation
  const currentIndex = index
  stateArr[index] = stateArr[index] || initialValue
  const updateState = newValue= > {
    stateArr[currentIndex] = newValue
    render()
  }
  
  // Avoid reworking the current value
  index += 1
  return [stateArr[currentIndex], updateState]
}

const render = () = > {
  // Each time render needs to reset the index
  index = 0
  ReactDom.render(<App />.document.getElementById('root'))}Copy the code

Conclusion:

  1. When the page is rendered for the first time, the useState execution in turn binds the corresponding updateState method to the corresponding index location and stores the initial value into stateArr.
  2. A user operation triggers updateState to update states under multiple indexes.
  3. Render again and execute useState in turn, except that the current stateArr already contains the value since the last state update. The value passed to the useState method is not the initial value, but the state value since the last update.

So much for our simple implementation. There are some big differences between useState and useState in react.

Note: The state in this method corresponds to memoizedState in React. Real React stores all states in a linked list instead of an array structure. The React Fiber implementation is of interest to you. React Fiber is a re-implementation of the React core algorithm (scheduling mechanism). An architecture dedicated to improving applicability to areas such as animation, layout, and gestures.

Note: The Hook and FunctionComponent Fiber both have memoizedState, so don’t confuse them. Fiber. MemoizedState: FunctionComponent corresponds to the Hooks saved in Fiber. Hook. memoizedState: Data for a single hook saved in the Hooks linked list.

How does react implement useState?

Explore the Inside of react

Define the hooks

Here you can see the Hook initialization properties in the reactFiberlinks.new.js file.

In order to avoid the react source code may load slowly due to github.com, the source code link is used in Gitee instead.

export type Hook = {|
  // Point to the current render node Fiber, the final state value since the last full update
  memoizedState: any,
  // Initialize the state and the new state after each dispatch
  baseState: any,
  // The Update that needs to be updated is assigned to the last Update after each Update, so react can render errors on the edge and backtrack data
  baseQueue: Update<any, any> | null.// Cache update queue, store multiple update behavior
  queue: UpdateQueue<any, any> | null.// Point to the next hook and concatenate each hook with next
  next: Hook | null|};Copy the code

Update is an object with an identifier that identifies what needs to be updated in react. The number of updates indicates how much react needs to Update next. For example, an Update object is created when the render function is called, or when the state method is called. Update objects are connected to each other via next, forming a one-way linked list data structure. The Update Ue is essentially a queue that holds the record Update.

Initialize the state

1. Dispatch

HooksDispatcherOnMount is the Dispatch that is performed on the first load.

Tips: Dispatch can be understood as one of the ways to change the internal state.

const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  // omit the other hooks.useState: mountState,
  ...
};
Copy the code

According to the code above, useState calls mountState when it is first loaded.

2. MountState

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

Recall that the mountState method here is a bit more insipid than the enhanced version of useState we just implemented. The mountWorkInProgressHook method creates and returns the corresponding hook. Note that the lastRenderedReducer value in the queue is basicStateReducer. That is, the reducer used by render last time was basicStateReducer.

3. BasicStateReducer

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

BasicStateReducer accepts two parameters: state and Action. It then returns either action(state) or action. Typeof Action === ‘function’ fails when we change the state by passing in a specific value (e.g. SetCount (520)) and returns action.

The action we pass in is a concrete value:

When the Setter is passed in a Reducer function:

Update the state

HooksDispatcherOnUpdate is the Dispatch that is performed when the update is performed. UseState here will do update Estate.

1. UpdateState

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

Here, the Update Estate will eventually return the Update Educer. And pass the basicStateReducer as the first argument.

2. UpdateReducer

function updateReducer<S.I.A> (reducer: (S, A) => S, initialArg: I, init? : I => S,) :S.Dispatch<A>] {
  // Get the current hook
  const hook = updateWorkInProgressHook();
  constqueue = hook.queue; . queue.lastRenderedReducer = reducer; .const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}
Copy the code

Remember that the hook of the mountState method is the mountWorkInProgressHook, and now the updateReducer uses the updateWorkInProgressHook. Possible causes are:

  • MountState is called only once during initialization.
  • UpdateReducer may be invoked repeatedly in event callbacks, side effects, and render phases.

The overall process: first find the corresponding hook, then calculate the state value of the hook according to update, and finally return.

Call stage dispatchAction

The last big module is the implementation of dispatchAction. You also saw the dispatchAction method in mountState, which is executed when useState is called.

function dispatchAction<S.A> (fiber: Fiber, queue: UpdateQueue
       
        , action: A,
       ,>) {.../ / create the update
  const update: Update<S, A> = {
    lane,
    action,
    eagerReducer: null.eagerState: null.next: (null: any),
  };

  // Append update to the end of the queue
  const pending = queue.pending;
  if (pending === null) {
    // This is the first update. Create a circular list.
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;

  const alternate = fiber.alternate;
  if( fiber === currentlyRenderingFiber || (alternate ! = =null && alternate === currentlyRenderingFiber)
  ) {
    // Update triggered by the render phase
    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  } else {
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      // Fiber update queue is empty
      if(lastRenderedReducer ! = =null) {... }}... scheduleUpdateOnFiber(fiber, lane, eventTime); }... }Copy the code

Create an UPDATE, append the update to the end of the queue, and start task scheduling.

CurrentlyRenderingFiber = currentlyRenderingFiber = currentlyRenderingFiber = currentlyRenderingFiber = currentlyRenderingFiber This means that this update occurs in the Render phase of the FunctionComponent corresponding to fiber.

Fiber. Lanes Stores update priorities in fiber task scheduling.

Fiber. Lanes === NoLanes Indicates that there is no update on fiber.

There may be multiple updates with different priorities on the hook, and the final state value is determined by the multiple updates. When there is no update on fiber, the update created is the first update on the hook, and only the update is used to initialize state, so a lot of unnecessary calculations can be avoided by conditional judgment. That is, if the calculated state is consistent with the state saved before the hook, there is no need to start a scheduling at all. Even if the calculated state is not the same as the state previously saved by the hook, the state already calculated by dispatchAction can be used directly when initializing and updating the state value.

conclusion

In a word: Create a hook when state is initialized. Update is inserted sequentially when dispatch is triggered. The Reducer is triggered when updateState is displayed.

Do you have a new understanding of useState now?

Finally, what the article didn’t say (but is worth expanding on) :

  • How does the React scheduling algorithm work?
  • Why a linked list structure?
  • How does useEffect work?

Refer to the article

  • Under-the-hood of React Hooks
  • React Technology revealed
  • React Hooks

Thank you

  • If this article helped you, give it a thumbs up! Thanks for reading.
  • If there are any mistakes in this article, please criticize and correct them in the comments section.
  • Image source network, if any copyright infringement please contact me to delete.