Implementation principle of React Hooks

  1. home page
  2. special column
  3. front end
  4. Article details
0

Implementation principle of React Hooks

HZFEStudio Published 6 minutes ago

Warehouse address of complete high frequency question bank: https://github.com/hzfe/awesome-interview

Complete high frequency question bank reading address: https://febook.hzfe.org/

Related issues

  • What is React Hooks
  • How is React Hooks implemented
  • What should I pay attention to when using React Hooks

Answer key points

Closure Fiber linked list

Hooks is a new feature in React 16.8. It allows you to use state and other React features without writing class.

Hooks mainly uses closures to save the state, uses the linked list to save a series of hooks, and associates the first Hook in the linked list with Fiber. When the Fiber tree is updated, the final output status and execution related side effects can be calculated from hooks.

Precautions for using Hooks:

  • Do not call Hooks in loops, conditions, or nested functions.
  • Call Hooks only in the React function.

In depth knowledge

1. Simplify implementation

Realization of React Hooks simulation

This example is a simplified simulation implementation of the React Hooks interface, which can be observed in actual operation. The react.js file simulates and implements the useState and useEffect interfaces, and its basic principle is similar to the actual implementation of react.

2. Comparative analysis

2.1 status Hook

In the simulated useState implementation, the state is saved in memoizedState[cursor] through closure. memoizedState is an array that can store the states generated by hook calls in order.

let memoizedState = [];
let cursor = 0;
function useState(initialValue) {
  // During the first call, the initial value passed in is used as state, and the state saved in the closure is used later
  let state = memoizedState[cursor] ?? initialValue;
  // The closure cache is performed on the cursor, so that when the setState is called, the corresponding state of the operation is correct
  const _cursor = cursor;
  const setState = (newValue) => (memoizedState[_cursor] = newValue);
  // The cursor is self incremented. When it is used for the hook called next, it refers to the new position in the memoizedState
  cursor += 1;
  return [state, setState];
}

The actual useState implementation goes through many aspects Comprehensive consideration , React finally chose to design Hooks as a sequential structure, which is why Hooks cannot be called conditionally.

function mountState<S>(
  initialState: (() => S) | S
): [S, Dispatch<BasicStateAction<S>>] {
  // Create a Hook and add the current Hook to the Hooks linked list
  const hook = mountWorkInProgressHook();
  // If the initial value is a function, call the function to get the initial value
  if (typeof initialState === "function") {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  // Create a linked list to store updated objects
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState,
  });
  // dispatch is used to modify the status and add this update to the update object linked list
  const dispatch: Dispatch<BasicStateAction<S>> = (queue.dispatch =
    (dispatchAction.bind(null, currentlyRenderingFiber, queue): any));
  return [hook.memoizedState, dispatch];
}

2.1 side effects Hook

The simulated useEffect implementation also uses the memoizedState closure to store dependent arrays. The default comparison algorithm is Object.is.

function useEffect(cb, depArray) {
  const oldDeps = memoizedState[cursor];
  let hasChange = true;
  if (oldDeps) {
    // Compare the passed in dependency array with the old dependency array saved in the closure, and use the shallow comparison algorithm
    hasChange = depArray.some((dep, i) => !Object.is(dep, oldDeps[i]));
  }
  if (hasChange) cb();
  memoizedState[cursor] = depArray;
  cursor++;
}

Actual useEffect implementation:

function mountEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null
): void {
  return mountEffectImpl(
    UpdateEffect | PassiveEffect, // fiberFlags
    HookPassive, // hookFlags
    create,
    deps
  );
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
  // Create a hook
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  // Set the side effect flag of workInProgress
  currentlyRenderingFiber.flags |= fiberFlags; // fiberFlags are marked to workInProgress
  // Create an Effect and mount it on hook.memoizedState
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags, // hookFlags is used to create an effect
    create,
    undefined,
    nextDeps
  );
}

3. How hooks works with Fiber

Before understanding how to work, take a look at some structural definitions of Hook and Fiber:

export type Hook = {
  memoizedState: any, // Latest status value
  baseState: any, // Initial state value
  baseQueue: Update<any, any> | null,
  queue: UpdateQueue<any, any> | null, // The ring linked list stores the update object generated by multiple calls of the hook
  next: Hook | null, // Next pointer, the next Hook in the linked list
};
export type Fiber = {
  updateQueue: mixed, // Stores a linked list of side effects related to the Fiber node
  memoizedState: any, // Stores the status values related to the Fiber node

  flags: Flags, // Identifies whether the current Fiber node has side effects
};

Different from the simulation implementation in the previous section, the real Hooks is a single linked list structure, and React adds the hook nodes to the linked list in turn according to the execution order of Hooks. Next, take useState and useEffect, the two most commonly used Hooks, as examples to analyze how Hooks and Fiber work together.

In each state Hook (such as useState) node, all update operations will be remembered through the circular linked list on the queue attribute, and all update operations in the circular linked list will be executed in turn in the updade stage, and finally the latest state will be returned.

The specific structure of the linked list composed of status Hooks is shown in the following figure:

In each side effect Hook (such as useEffect) node, create an effect, mount it to the memoizedState of the Hook, and add it to the end of the ring linked list. The linked list will be saved to the updateQueue of the Fiber node and executed in the commit phase.

The specific structure of the linked list composed of side effects Hooks is shown in the following figure:

reference material

  1. Why Do React Hooks Rely on Call Order?
  2. React hooks: not magic, just arrays
Reading 8 was released 6 minutes ago
Like collection
19 prestige
2 Fans
Focus on the author
Submit comments
You know what?

Register login
19 prestige
2 Fans
Focus on the author
Article catalog
follow
Billboard

Keywords: Front-end React linked list react-hooks

Added by jfourman on Sat, 30 Oct 2021 10:28:41 +0300