React-hooks Function Component Design

Why learn the React Hooks principle

First, utilitarian: there are currently three different front-end frameworks: React, Vue, Angular, and React. Since v16.8.0 introduced the React Hooks concept, the trend has shifted sharply from class components to function components. This is a very recent innovation at the design pattern, mental model level. So whenever you talk about React, you will be asked in an interview about the principle of React Hooks.

Furthermore, from a practical point of view, understanding the React Hooks principle is of great benefit to daily development and debugging. Remember that React Hooks are not black magic, and the weird problems we had in development were caused by React Hooks that we didn’t yet understand and didn’t need some tricky method to fix.

useState / useReducer

Both useState and useReducer are about extracting and updating state values. In essence, there is no difference between useState and useReducer. In terms of implementation, it can be said that useState is a simplified version of useReducer, which uses the same logic behind it.

How do React Hooks save state

React Hooks save state in the same location as class components. After reviewing the source code, I found that this statement is correct, but incomplete:

  • Both state values are mounted on the component instance objectFiberNodethememoizedStateAttribute.
  • The data structures for storing state values are completely different; The class component saves the developer’s custom object directly to the state propertymemoizedStateAttribute; React Hooks save state in linked lists,memoizedStateProperty actually holds the header pointer to the list.

Let’s see what the nodes of the list look like — Hook objects:

// react-reconciler/src/ReactFiberHooks.js
export type Hook = {
  memoizedState: any.// The latest status value
  baseState: any.// The initial state value, such as' useState(0) ', is 0
  baseUpdate: Update<any.any> | null,
  queue: UpdateQueue<any.any> | null.// The operation that temporarily saves a state value is more accurately a pointer to a linked list data structure
  next: Hook | null.// points to the next list node
};
Copy the code

The official documentation always states that calls to React Hooks should only be placed at the top of the function component/custom Hooks function body, because we can only relate the actual saved data structure to the order in which Hooks are called:

PS: While useState and useReducer are used as examples, all React Hooks are saved in this way.

How do React Hooks update status

Familiar with the useState API, we all know how to update the status:

const [name, setName] = useState(' ')
setName('Joe')
Copy the code

So how does the function returned by useState to update the status (hereafter called dispatcher) work?

When we call the Dispatcher, we do not immediately modify the status value (yes, the update of the status value is asynchronous), but create a change operation — add a new node to the list mounted by the queue property of the corresponding Hook object:

React calculates the latest state values from the list of update operations mounted on each Hook the next time the function component is called useState. Why, you might wonder, should you save all updates, just the latest update? You might have forgotten that useState supports syntax like this:

const [name, setName] = useState(' ')
setName(name= > name + 'a')
setName(name= > name + 'b')
setName(name= > name + 'c')

// The next time I execute the name, I will get the latest status value of 'ABC'
Copy the code

useEffect

UseEffect is stored in a similar way to useState/useReducer and is mounted in FiberNode.update Ue as a linked list.

UseEffect is implemented by the mount and update component lifecycle:

Mount stage: Mount ect

  1. Build a linked list based on the useEffect statements that are called sequentially in the body of the function component and mount it inFiberNode.updateQueue, the data structure of the linked list node is:
 const effect: Effect = {
    tag, // used to indicate whether a dependency has changed
    create, // The user uses useEffect to pass in the function body
    destroy, // The function generated after the above function body is executed to remove side effects
    deps, // List of dependencies
    next: (null: any),};Copy the code
  1. After the component completes rendering, the list is traversed for execution.

Update phase: updateEffect

  1. The useEffect statement is also called in turn to determine which dependency list and linked list nodes are passed inEffect.depsAre the values of the base data types the same; Object references are the same), if they are, inEffect.tagOn the tagNoHookEffect.

Execution phase

Function commitHookEffectList() :

  1. Traversing the list
  2. If you encounterEffect.tagIs marked on theNoHookEffectIs skipped.
  3. ifEffect.destroyIs a function type, the function that clears the side effects needs to be executedEffect.destroyWhere did it come from, more on that in a moment)
  4. performEffect.createAnd save the execution result toEffect.destroy(If not configured by the developerreturnOf course, what you get isundefinedThat is, the developer believes that there are no side effects that need to be removed for the current useEffect code segment); Notice that because of closures,Effect.destroyYou can actually access thisEffect.createA variable in the scope of a function.

It is important to note that the effects of the previous round are cleared before the effect of this round is executed.

Other React Hooks apis

The React Hooks Api does much the same thing: use linked list data structures for global state retention; Determining dependencies to decide whether to update the state and so on will not be covered here.

conclusion

The paper expounds the React by more refined language to the principle of Hooks, the goal is to make readers have a perceptual knowledge, also easy to cope with an interview; In fact, React Hooks have a lot more implementation details, read the source code here.