An overview of the

React Hooks are provided in V16.8. React Hooks inject functionality into Function Components by enhancing them. For example, useState makes the Stateless Function Component state.

The working principle of

Let’s start with the useState hook and open the React Hooks source code to see how it works. Let’s start with a Demo:

import React, { useState }  from 'react';

function App() {
  const [count, dispatchCount] = useState(0);

  return (
    <div>
      <span>{count}</span>
      <button onClick={()= > dispatchCount(count + 1)}>
		  	increment
	  	</button>
    </div>)}Copy the code

The above Demo defines a counter using a function component that, relative to a normal function component, provides the state of count, increment by one each time the button is clicked. Let’s take a look at the source code for useState and see how it saves and updates the count state.

State saving and updating

After removing the extraneous code, you can see that our call to useState is just an entry point and ultimately a method to call the Dispatcher. And in react.js it only does definitions.

// react/src/ReactHooks.js
const ReactCurrentDispatcher = {
  / * * *@internal
   * @type {ReactComponent}* /
  current: (null: null | Dispatcher),
};

export function useState<S> (
  initialState: (() => S) | S,
) :S.Dispatch<BasicStateAction<S> >]{
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return dispatcher;
}
Copy the code

Hooks are only called when FunctionalComponent is updated. RenderWithHooks are the Hook update entry in the updateFunctionComponent method. In accordance with the conditions on the ReactCurrentDispatcher in renderWithHooks. Current assignment.

 // react-reconciler/src/ReactFiberHooks.js 
function renderWithHooks(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  props: any,
  secondArg: any,
  nextRenderExpirationTime: ExpirationTime,
) {
	/ /... Omit irrelevant code
	ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
		// Mount
        ? HooksDispatcherOnMount
		// Update
        : HooksDispatcherOnUpdate;
	// ...
}

Copy the code

You can see that Dispatcher is divided into Mount and Update. Here we can find the corresponding mountState and updateState, and other hooks are also classified in this way. If we want to view other hook codes, we can find the corresponding time codes here.

State of preservation

Before we look at the code, let’s look at the definition of Hook.

Hook = {
  // The current hook state is the same as the count value in the Demo above
  memoizedState: any,
  // Multiple calls to save the queue
  queue: UpdateQueue<any, any> | null.// The next hook is a one-way list of hooks connected by this property
  next: Hook | null|};Copy the code

Hook objects within a component are stored in memoizedState of the Fiber object corresponding to the App component. The preservation structure is roughly as follows:

fiber = {
	memoizedState: {
		memoizedState: initialState,
	  	queue: {},
	 	next: {
			memoizedState: initialState,
			queue: {},
			next: null,}}}Copy the code

Status updates

When mountState is called for the first rendering, [hook.memoizedState, Dispatch] is returned.

// react-reconciler/src/ReactFiberHooks.js 
// Get the hook object
function mountWorkInProgressHook() :Hook {
  const hook: Hook = {
    memoizedState: null.baseState: null.baseQueue: null.queue: null.next: null};if (workInProgressHook === null) {
    // Add hook to Fibre. memoizedState
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Insert the list to specify the next hook
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

function mountState<S> (
  initialState: (() => S) | S,
) :S.Dispatch<BasicStateAction<S> >]{
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  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

Bind (null, currentlyRenderingFiber, queue)

function dispatchAction<S.A> (fiber: Fiber, queue: UpdateQueue
       
        , action: A,
       ,>) {
  	// Each time dispatchCount is called, an update object is created to record the action value to be updated
  	const update: Update<S, A> = {
		action,
		next: null.// ...
	  };
	// ...
    // Append the update to the end of the list
  const pending = queue.pending;
  if (pending === null) {
    // This is the first update, creating a circular list.
    update.next = update;
  } else {
	// Insert a new update node
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
  // ...
  // Update render scheduling
  scheduleWork()
}
Copy the code

Update the state

When dispatchCount is called, updateState is actually called to merge the state. In updateReducer, the hook list is iterated to get the latest memoizedState and returned.

// react-reconciler/src/ReactFiberHooks.js 
function updateState<S> (
  initialState: (() => S) | S,
) :S.Dispatch<BasicStateAction<S> >]{
  return updateReducer(basicStateReducer, (initialState: any));
}

function updateReducer<S.I.A> (reducer: (S, A) => S, initialArg: I, init? : I => S,) {
  	const hook = updateWorkInProgressHook();
  	const queue = hook.queue;
  	// ...
  	let first = baseQueue.next;
  
	do {
	  // Get the state action passed in
	  const action = update.action;
	  / / update the state
	  newState = reducer(newState, action);
	  // Iterate over the next update action
	  update = update.next;
	} while(update ! = =null&& update ! == first) hook.memoizedState = newState;const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}

Copy the code

summary

For useState logic, this is equivalent to the state of the original class component, except that in the function component, its state resides on the Fiber node. Update the state in the function component by iterating through and updating the hook object under the Fiber node through the linked list operation.