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:
- The previous Reconciler in Fiber (known as Stack Reconciler) is a top-down recursive process
mount/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 - 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.react
Packages 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,renderWithHooks
Functions 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
memoizedState
Is used to record the currentuseState
Should return the resultqueue
: cache queue that stores multiple update behaviornext
: Point to next timeuseState
Corresponding 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 call
setCount(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 in
updateReducer
Is 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:
- Call the dispatcher function and insert update(which is an action) in order.
- Collect updates and schedule React updates
- In the process of updating
ReactCurrentDispatcher.current
Point to the Dispatcher responsible for updates - When the function component App() is executed,
useState
The Resolve Dispatcher phase gets the dispatcher responsible for the update. useState
Will get a Hook object,Hook.query
The update queue is stored in. After the update, the latest state can be obtained- The count value in nextChild returned after execution by the function component App() is already up to date. In the FiberNode
memorizedState
It is also set to the latest state - 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🌟