- home page
- special column
- front end
- Article details
Implementation principle of React Hooks
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: