If foreplay is too long, you can start with chapter 3

React 16.8.6 explains how to React

Example code to use:

import React, { useState } from 'react'
import './App.css'

export default function App() {
  
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Star');
  
  Call setCount three times to see how the queue is being updated
  const countPlusThree = (a)= > {
    setCount(count+1);
    setCount(count+2);
    setCount(count+3);
  }
  return (
    <div className='App'>
      <p>{name} Has Clicked <strong>{count}</strong> Times</p>
      <button onClick={countPlusThree}>Click *3</button>
    </div>)}Copy the code

The code is very simple, click the button to make count+3, and the value of count is displayed on the screen.

1. Pre-knowledge

1. Function components and class components

How Are Function Components Different from Classes?

Main Concepts of this section:

  • The difference between functional components and class components
  • How does React distinguish between these two components

Let’s look at a simple Greeting component that supports defining both classes and functions. Don’t worry about how he defines it when you use it.

// Class or function -- it doesn't matter
<Greeting />  // <p>Hello</p>
Copy the code

If Greeting is a function, React needs to call it.

// Greeting.js
function Greeting() {
  return <p>Hello</p>;
}

/ / the React within
const result = Greeting(props); // <p>Hello</p>
Copy the code

But if Greeting is a class, React needs to instantiate it first and then call the Render method that generated the instance:

// Greeting.js
class Greeting extends React.Component {
  render() {
    return <p>Hello</p>; }}/ / the React within
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>
Copy the code

React uses the following methods to determine component types:

/ / the React within
class Component {}
Component.prototype.isReactComponent = {};

// Check mode
class Greeting extends React.Component {}
console.log(Greeting.prototype.isReactComponent); / / {}
Copy the code

2. React Fiber

A Cartoon intro to Fiber

The main concepts of this section are as follows:

  • React renders are now scheduled by Fiber
  • Two phases in Fiber scheduling process (bounded by Render)

Fiber, a finer granularity of control than thread, is a new feature in React 16 designed to fine-tune the rendering process.

Causes:

  1. The previous Reconciler in Fiber (known as Stack Reconciler) is a top-down recursive processmount/update, cannot be interrupted (continues to occupy the main thread), so that the layout, animation and other periodic tasks and interactive responses on the main thread cannot be handled immediately, affecting the experience
  2. There is no priority in rendering

React Fiber

A long task is divided into many small pieces, each of which has a short running time. Although the total time is still very long, other tasks are given a chance to execute after each small piece is executed, so that the only thread is not monopolized and other tasks still have a chance to run.

The React Fiber fragmented the update process, as shown in the figure below. After each update process, control is handed back to the React task coordination module to see if there are any other urgent tasks to be done. If there are no urgent tasks to be done, do the urgent tasks.

The data structure that maintains each shard is called Fiber.

After sharding, the call stack of the update process is shown in the figure below. Each trough in the middle represents the execution process of a sharding, and each peak is the time when the control is handed back after the execution of a sharding. Let the thread do something else

Fiber’s scheduling process is divided into the following two stages:

Render/Reconciliation phase – All life cycle functions can be executed multiple times, so try to keep the state constant

  • componentWillMount
  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate

Commit phase – Cannot be interrupted and only executes once

  • componentDidMount
  • componentDidUpdate
  • compoenntWillunmount

The incremental update of Fiber requires more context information than the previous vDOM tree, so we extend the Fiber tree (vDOM tree for Fiber context). The update process is to construct a new Fiber Tree (workInProgress Tree) from the input data and the existing fiber tree.

All codes relating to Fiber are located in Packages/React-Reconciler, and a Fiber node is defined in detail as follows:

function FiberNode(tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,) {
  // Instance
  this.tag = tag; this.key = key; this.elementType = null; 
  this.type = null; this.stateNode = null;

  // Fiber
  this.return = null; this.child = null; this.sibling = null; 
  this.index = 0; this.ref = null; this.pendingProps = pendingProps;
  this.memoizedProps = null; this.updateQueue = null;
  
  / / the key
  this.memoizedState = null;
  
  this.contextDependencies = null; this.mode = mode;

  // Effects
  /** **/
}
Copy the code

We’ll just focus on this. MemoizedState

This key is used to store the state of the node that was finally obtained during the last render. Before each render, React calculates the latest state of the current component and assigns it to the component before executing render. – Class components and function components that use useState.

Remember that, and memoizedState will be mentioned a lot later

For details about each key in Fiber, see the source code notes

React renderer and setState

How Does setState Know What to Do?

Main Concepts of this section:

  • What is the React renderer
  • Why does setState trigger updates

Due to the complexity of React architecture and the diversity of target platforms.reactPackages only expose apis that define components. Most React implementations exist in renderers.

React-dom, react-dom/server, react-native, react-test-renderer, and react-Art are common renderers

That’s why the React package is available regardless of the target platform. Everything exported from the React package, such as react.ponent, react.createElement, react.children, and Hooks, is platform independent. Components can be imported and used the same way whether they are running the React DOM, React DOM Server, or React Native.

So when we want to use new features, react and react-dom both need to be updated.

For example, when React 16.3 adds the Context API, the React.createcontext ()API is exposed by the React package. But React. CreateContext () doesn’t actually implement context. The React DOM and React DOM Server should have different implementations of the same API. So createContext() only returns normal objects: ** So, if you upgrade React to 16.3+ but don’t update the React – DOM, you’re using a renderer that doesn’t yet know the Provider and Consumer types. ** This is why older versions of react-dom reported an error saying that these types were invalid.

This is why setState, although defined in the React package, can be called to update the DOM. It reads this.updater set by the React DOM and lets the React DOM schedule and process updates.

Component.setState = function(partialState, callback) {
  // All setState does is delegate the renderer to create an instance of this component
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Copy the code

The updater in each renderer triggers updated rendering for different platforms

// React DOM internal
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMUpdater;

// React DOM Server internal
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMServerUpdater;

// React Native internal
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactNativeUpdater;
Copy the code

As for the implementation of updater, which is not the point here, let’s get to the subject of this article: React Hooks

Learn about useState

1. Introduction and trigger update of useState

Main Concepts of this section:

  • How is useState introduced and invoked
  • Why can useState trigger component updates

All Hooks are imported in react. js, mounted in the React object

// React.js
import {
  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useDebugValue,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
} from './ReactHooks';
Copy the code

Let’s go to Reacthooks.js and see that the useState implementation is surprisingly simple, just two lines long

// ReactHooks.js
export function useState<S> (initialState: (() = >S) | S) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}
Copy the code

Seems to focus on the dispatcher, the dispatcher through resolveDispatcher () to obtain, this function also is very simple, just ReactCurrentDispatcher. Current value is assigned to the dispatcher

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

So useState (XXX) equivalent to ReactCurrentDispatcher. Current. UseState (XXX)

Look back at the React renderer and setState in section 3 of Chapter 1.

Similar to updater is the core of the setState can trigger updates, ReactCurrentDispatcher. Current. UseState is a key reason useState can trigger updates, the realization of this method does not react in bags. Let’s look at an example of a specific update.

2. Example analysis

Take the code given at the beginning of this article as an example.

Let’s start with the beginning of Fiber scheduling: ReactFiberBeginwork

As mentioned earlier, React has the ability to differentiate between different components, so it tags different component types differently. See shared/ reactWorktags.js for details.

So in the beginWork function, you can load or update components in different ways based on the tag value in the workInProgess(which is a Fiber node).

// ReactFiberBeginWork.js
function beginWork(current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime,) :Fiber | null {
  /** ** **/

  // Use different methods for different component types
  switch (workInProgress.tag) {
    // Uncertain component
    case IndeterminateComponent: {
      const elementType = workInProgress.elementType;
      // Load the initial component
      return mountIndeterminateComponent(
        current,
        workInProgress,
        elementType,
        renderExpirationTime,
      );
    }
    // Function components
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      // Update the function component
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    / / class components
    case ClassComponent {
      /** **/}}Copy the code

Let’s find out where useState comes in.

2.1 First Loading

Mount process execution mountIndeterminateComponent, performs to renderWithHooks this function

function mountIndeterminateComponent(_current, workInProgress, Component, renderExpirationTime,) {
 
 /** omit preparation phase code **/ 
  
  // Value is the rendered APP component
  let value;

  value = renderWithHooks(
    null,
    workInProgress,
    Component,
    props,
    context,
    renderExpirationTime,
  );
  /** omit irrelevant code **/ 
  }
  workInProgress.tag = FunctionComponent;
  reconcileChildren(null, workInProgress, value, renderExpirationTime);
  return workInProgress.child;
}
Copy the code

NextChildren = value

After execution: value= virtual DOM representation of the component

How does this value get rendered as a real DOM node, we don’t care, the state value we got and rendered by renderWithHooks

The 2.2 update

Click the button: count changes from 0 to 3

The update executes the updateFunctionComponent function, which also executes renderWithHooks. Let’s see what happens after this function executes:

NextChildren = undefined

NextChildren = Virtual DOM representation of the updated component

Again, we don’t care how this nextChildren is rendered as a real DOM node. The latest state value we’ve retrieved and rendered using renderWithHooks

So,renderWithHooksFunctions are at the heart of handling the hooks logic

Iii. Analysis of core steps

Reactfiberlinks.js contains various Hooks logic handling, and the code in this chapter comes from that file.

1. The Hook object

As mentioned in the previous section, memorizedStated in Fiber is used to store state

In the class component, State is an entire object that corresponds to memoizedState. In Hooks, React doesn’t know how many times we called useState, so React keeps the state of the function component by mounting a Hook object on memorizedState

The structure of a Hook object is as follows:

// ReactFiberHooks.js
export type Hook = {
  memoizedState: any, 

  baseState: any,    
  baseUpdate: Update<any, any> | null.queue: UpdateQueue<any, any> | null.next: Hook | null};Copy the code

Focus on memoizedState and Next

  • memoizedStateIs used to record the currentuseStateShould return the result
  • queue: cache queue that stores multiple update behavior
  • next: Point to next timeuseStateCorresponding Hook object.

Combine this with the sample code:

import React, { useState } from 'react'
import './App.css'

export default function App() {
  
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Star');
  
  Call setCount three times to see how the queue is being updated
  const countPlusThree = (a)= > {
    setCount(count+1);
    setCount(count+2);
    setCount(count+3);
  }
  return (
    <div className='App'>
      <p>{name} Has Clicked <strong>{count}</strong> Times</p>
      <button onClick={countPlusThree}>Click *3</button>
    </div>)}Copy the code

The first time you click the button to trigger the update, memoizedState looks like this

This is consistent with the previous analysis of Hook structures, but the queue structure seems a bit strange, which we will examine in Chapter 3, Section 2.

2. renderWithHooks

RenderWithHooks run like this:

// ReactFiberHooks.js
export function renderWithHooks(current: Fiber | null, workInProgress: Fiber, Component: any, props: any, refOrContext: any, nextRenderExpirationTime: ExpirationTime,) :any {
  renderExpirationTime = nextRenderExpirationTime;
  currentlyRenderingFiber = workInProgress;
 
  // If current is null, no hook objects have been mounted
  MemoizedState refers to the next current. MemoizedStatenextCurrentHook = current ! = =null ? current.memoizedState : null;

  // Use the nextCurrentHook value to distinguish mount from update, set different dispatcher
  ReactCurrentDispatcher.current =
      nextCurrentHook === null
      // during initialization
        ? HooksDispatcherOnMount
  		/ / update
        : HooksDispatcherOnUpdate;
  
  // Now that you have a new Dispatcher, you can get the new object when you call Component
  let children = Component(props, refOrContext);
  
  / / reset
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;

  const renderedWork: Fiber = (currentlyRenderingFiber: any);

  // Update memoizedState and updateQueue
  renderedWork.memoizedState = firstWorkInProgressHook;
  renderedWork.updateQueue = (componentUpdateQueue: any);
  
   /** ** **/
}
Copy the code

2.1 Initialization

Core: Create a new hook, initialize state, and bind the trigger

Initialization phase ReactCurrentDispatcher. Current will point HooksDispatcherOnMount object

// reactFiberhooks.js const HooksDispatcherOnMount: Dispatcher = {/** omit other Hooks **/ useState: mountState,}; / / so call useState. (0) is returned by the HooksDispatcherOnMount useState (0), namely mountState (0) function mountState < S > (initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] {const Hook = mountWorkInProgressHook(); If (typeof initialState === 'function') {initialState = initialState(); BaseState = initialState; // memoizedState = memoizedState; Const queue = (hook. Queue = {last: null, dispatch: null, eagerReducer: Reducerstate: (initialState: any),}); // Return trigger const dispatch: Dispatch<BasicStateAction<S>,> = (queue.dispatch = (dispatchAction.bind( null, Queue ((currentlyRenderingFiber: any): fiber), queue,)); // Return the initial state and trigger return [hook.memoizedState, dispatch]; } // For update actions triggered by useState (assuming all variables passed in useState), BasicStateReducer <S>(state: S, action: BasicStateAction<S>): S { return typeof action === 'function' ? action(state) : action; }Copy the code

Focus on the update function dispatchAction that is returned

function dispatchAction<S.A> (fiber: Fiber, queue: UpdateQueue
       
        , action: A,
       ,>) {

   /** omit Fiber scheduling code **/
  
  Update, action is the value of setCount (count+1, count+2, count+3...).
    const update: Update<S, A> = {
      expirationTime,
      action,
      eagerReducer: null.eagerState: null.next: null};// Focus: build query
    // queue.last is the last update, and last.next starts with each action
    const last = queue.last;
    if (last === null) {
      // There is only one update, which refers to itself - form a loop
      update.next = update;
    } else {
      const first = last.next;
      if(first ! = =null) {
        
        update.next = first;
      }
      last.next = update;
    }
    queue.last = update;

    /** omit special case code **/
    
    // Create an update task
    scheduleWork(fiber, expirationTime);

}
Copy the code

A Query data structure is maintained in dispatchAction.

Query is a ring-linked list with the following rules:

  • Query.last points to the last update
  • Last.next points to the first update
  • And so on, until the penultimate update points to last, forming a loop.

So every time you insert a new update, you need to point the original first to Query.last.next. Update points to Query.next, and query.last points to update.

The following illustration is illustrated with sample code:

The query value in memorizedState is given earlier when the button is updated for the first time

Its construction process is shown in the figure below:

Ensure that Query.last is always the latest action and query.last.next is always action: 1

2.2 update

Core: Get the queue in the Hook object, which contains a series of data for this update, and update it

Update phase ReactCurrentDispatcher. Current will point HooksDispatcherOnUpdate object

// ReactFiberHooks.js

/ / so call useState. (0) is returned by the HooksDispatcherOnUpdate useState (0), namely updateReducer (basicStateReducer, 0)

const HooksDispatcherOnUpdate: Dispatcher = {
  /** omit other Hooks **/
   useState: updateState,
}

function updateState(initialState) {
  return updateReducer(basicStateReducer, initialState);
}

// We can see that the updateReducer process has nothing to do with the initalState passed, so the initial value is used only the first time

// Delete some irrelevant code for easy reading
// View the complete code: https://github.com/facebook/react/blob/487f4bf2ee7c86176637544c5473328f96ca0ba2/packages/react-reconciler/src/ReactFiber Hooks.js#L606
function updateReducer(reducer, initialArg, init) {
// Get the initialization hook
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;

  // Start rendering update
  if (numberOfReRenders > 0) {
    const dispatch = queue.dispatch;
    if(renderPhaseUpdates ! = =null) {
      // Get the queue on the Hook object that contains the data for this update
      const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
      if(firstRenderPhaseUpdate ! = =undefined) {
        renderPhaseUpdates.delete(queue);
        let newState = hook.memoizedState;
        let update = firstRenderPhaseUpdate;
        // Get the updated state
        do {
          const action = update.action;
          // The reducer is the basicStateReducer, and the reducer directly returns the action value
          newState = reducer(newState, action);
          update = update.next;
        } while(update ! = =null);
        // Update hook.memoized
        hook.memoizedState = newState;
        // Return the new state and update the hook's dispatch method
        return[newState, dispatch]; }}}BasicStateReducer returns the value of the update action triggered by useState (assuming all variables passed in useState)
function basicStateReducer<S> (state: S, action: BasicStateAction<S>) :S {
  return typeof action === 'function' ? action(state) : action;
}
Copy the code

2.3 summarize

The update behavior of individual hooks is all tied to rests.queue, so the essence of managing the queue is to manage it well

  • Example Initialize queue-mountState
  • Maintain queue-dispatchAction
  • Update queue – updateReducer

Combined with sample code:

  • When we first call[count, setCount] = useState(0), create a queue
  • Every callsetCount(x)To dispach an action (count = x) that is not dispach, the action is stored in a queue and maintained by the same rules as above
  • These actions end up inupdateReducerIs called, updated tomemorizedState, so that we can get the latest state value.

4. To summarize

1. Understanding of Rules of Hooks in official documents

The official documentation has two requirements for using hooks:

2.1 Why cannot it be executed in a loop/conditional statement

Take useState for example:

Unlike a class component that stores state, React doesn’t know how many times we called useState. The hooks are stored sequentially (see Hook structure), and the next of a Hook object points to the next hooks. So when we establish the correspondence in the sample code, the Hook structure is as follows:

// hook1: const [count, setCount] = useState(0) -- state1
{
  memorizedState: 0
  next : {
    // hook2: const [name, setName] = useState('Star') - state2
    memorizedState: 'Star'
    next : {
      null}}}// hook1 => Fiber.memoizedState
// state1 === hook1.memoizedState
// hook1.next => hook2
// state2 === hook2.memoizedState
Copy the code

So if you put HOOk1 in an if statement, when this is not executed, hook2 actually gets the state from the last HOOk1 execution (not from the last HOOk2 execution). This is obviously an error.

Read this article if you want to learn more about this topic

2.2 Why can HOOKS only be used in function components

Only updates to function components trigger the renderWithHooks function, which handles Hooks related logic.

Using setState as an example, class components and function components have different logic for rerendering:

Class component: Trigger the updater with setState to re-execute the Render method in the component

Function components: Use the setter function returned by useState to dispatch an Update action, trigger the update (dispatchAction last scheduleWork), and use updateReducer to handle the update logic. Returns the latest state value (similar to Redux)

2. Summary of useState’s overall operation process

Having said so much, we will briefly summarize the implementation process of useState

Initialization: Build the Dispatcher function and initialization values

Update:

  1. Call the dispatcher function and insert update(which is an action) in order.
  2. Collect updates and schedule React updates
  3. In the process of updatingReactCurrentDispatcher.currentPoint to the Dispatcher responsible for updates
  4. When the function component App() is executed,useStateThe Resolve Dispatcher phase gets the dispatcher responsible for the update.
  5. useStateWill get a Hook object,Hook.queryThe update queue is stored in. After the update, the latest state can be obtained
  6. The count value in nextChild returned after execution by the function component App() is already up to date. In the FiberNodememorizedStateIt is also set to the latest state
  7. Fiber renders the real DOM. The update is complete.

About us:

We are ant Insurance experience technology team, from ant Financial Insurance business group. We are a young team (no historical technical stack baggage), the current average age of 92 years (excluding a maximum score of 8x years – team leader, excluding a minimum score of 97 years – intern junior brother). We support almost all of Ali Group’s insurance businesses. In 18 years, our mutual treasure made a stir in the insurance industry, and in 19 years, we have several heavyweight projects in preparation and mobilization. Now with the rapid development of the business group, the team is also expanding rapidly, welcome to join us

We want you to be: technically solid, in-depth in a field (Node/ interactive marketing/data visualization, etc.); Good at precipitation and continuous learning in learning; Optimistic and outgoing personality.

If you are interested in joining us, please send your resume to [email protected]


Author: Ant Insurance – Experience Technology Group – Star Flame

Gold nuggets: STAR🌟