preface

I’ve been preparing for an interview. After reviewing some knowledge points on React, I hereby summarize.

start

React lifecycle

The react 16 lifecycle was like this before

When a component is first rendered, it is instantiated and then calls the componentWillMount, Render, and componentDidMount functions on the instance. Component can call componentWillReceiveProps, when update rendering shouldComponentUpdate, componentWillUpdate, render and componentDidUpdate function. Components can be unmounted by calling the componentWillUnmount function.

Borrow figure:

React uses getDerivedStateFromProps and getSnapshotBeforeUpdate as life cycle functions instead of componentWillMount. ComponentWillReceiveProps and componentWillUpdate function three life cycle. It is important to note that the two new lifecycle functions and the original three lifecycle functions must be used separately

Current life cycle (by photo) :

Problems with componentWillMount

Some people think that in componentWillMount can advance asynchronous requests to avoid white screen. However, when React calls Render to render the page, Render does not wait for the end of the asynchronous request before getting the data to render. There are potential pitfalls.

After React Fiber it might be called multiple times in one render. The reason: React Fiber technology uses incremental rendering to solve the problem of frame drop. RequestIdleCallback schedules the execution of each task unit, which can be interrupted and resumed. Once the life cycle is interrupted, it will be resumed and run the previous life cycle again

New life cycle

static getDerivedStateFromProps

  • Trigger time (fixed in V16.4) : Every time the component is rerender, including after the component is built (last executed before Render), and every time a new props or state is obtained. At v16.3, updates to component State did not trigger this lifecycle
  • Each time a new props is received, an object is returned as the new state. Returning NULL means that the state does not need to be updated
  • Cooperate with componentDidUpdate, can cover all usage of componentWillReceiveProps

getSnapshotBeforeUpdate

  • Trigger time: When the update occurs, after render and before component DOM rendering.
  • Returns a value as the third argument to componentDidUpdate.
  • With componentDidUpdate, you can override all uses of componentWillUpdate.

React Fiber

Because the React render/update process cannot be interrupted once it starts, it continues to occupy the main thread, which is too busy executing JS to care (layout, animation), resulting in frame drop, delayed response (or even no response), and other bad experiences. Fiber was born.

Fiber is a reconstruction of the React Reconciler’s core algorithm. Key features are as follows:

  • Incremental render (divide the render task into blocks, evenly spaced to multiple frames)
  • Update can pause, terminate, and reuse rendering tasks
  • Give priority to different types of updates
  • New foundational capabilities for concurrency

Incremental rendering is used to solve the problem of losing frames. The rendering task is split up and done in small sections at a time, leaving time control back to the main thread instead of taking longer.

Fiber tree

  • The top-down recursive mount/ Update of the Reconciler (known as the Stack Reconciler) before the Fiber is uninterruptible (continuously occupying the main thread), so that periodic tasks such as layout, animation, and interactive responses on the main thread cannot be handled immediately, affecting the experience.

  • Fiber solves this problem by breaking the render/update process (recursive diff) into a series of small tasks. Each time you check a small part of the tree, see if you have time to move on to the next task, continue if you do, hang yourself if you don’t, and continue when the main thread isn’t busy.

Fiber tree is actually a single linked list structure, with child pointing to the first child, return to the parent, and sibling pointing to the next sibling. The structure is as follows:

// Fiber tree {stateNode, child, return, sibling,... }Copy the code

Fiber reconciler

The reconcile process is divided into two stages:

1. (interruptible) Render /reconciliation through the construction of the workInProgress tree to get change

Commit these DOM changes (updating the DOM tree, calling component lifecycle functions, and updating internal states such as refs)

The process of building the workInProgress Tree is diff’s process, scheduling and executing a set of tasks via requestIdleCallback. After completing a task, it checks back to see if there are any (more urgent) queue-jumperers. After completing a set of tasks, it gives control of time back to the main thread. Continue building the workInProgress tree until the next requestIdleCallback callback

The life cycle is also divided into two phases:

/ / phase 1 render/reconciliation componentWillMount componentWillReceiveProps shouldComponentUpdate componentWillUpdate / / Phase 2 Commit componentDidMount componentDidUpdate componentWillUnmountCopy the code

Phase 1 lifecycle functions may be called multiple times and are executed at low priority by default. If interrupted by a higher-priority task, they will be executed again later.

Fiber tree and workInProgress tree

Double buffering technology: Fiber (); fiber (); workInProgress (); fiber (); The old fiber is used as the space for the new fiber to be updated, so that instances of the fiber can be reused.

Each fiber has an alternate property, which also points to a fiber. Alternate is preferred when creating workInProgress nodes. If not, create one

let workInProgress = current.alternate;
if (workInProgress === null) {
  //...
  workInProgress.alternate = current;
  current.alternate = workInProgress;
} else {
  // We already have an alternate.
  // Reset the effect tag.
  workInProgress.effectTag = NoEffect;

  // The effect list is no longer valid.
  workInProgress.nextEffect = null;
  workInProgress.firstEffect = null;
  workInProgress.lastEffect = null;
}
Copy the code

Benefits:

  • Ability to reuse internal objects (fiber)
  • Save time for memory allocation and GC

Fiber interrupt recovery

Interrupt: Check the current unit of work being processed, save the current result (firstEffect, lastEffect), modify the tag, quickly close and open a requestIdleCallback, and do it again at the next opportunity

Breakpoint recovery: The next time you deal with the unit of work, see that the tag is the interrupted task and proceed with the unfinished part or redo

P.S. It’s the same for the interrupt mechanism whether it’s a “natural” interruption when time runs out or a rude interruption by a superior task.

React setState

After the setState function is called in the code, React merges the passed parameter objects with the component’s current state and triggers a Reconciliation process. After the concatenation process, React builds the React element tree based on the new state in a relatively efficient way and starts to rerender the entire UI. After React gets the element tree, React automatically calculates the node differences between the new tree and the old tree, and then minimizes the rerendering of the interface based on the differences. In the differential calculation algorithm, React is able to know with relative accuracy which positions have changed and how, which ensures that it is updated on demand rather than re-rendering everything.

The setState call is sometimes synchronous (setTimeout, custom DOM event) and sometimes asynchronous (normal call).

React event mechanism

React events are distributed uniformly on the outermost document through the event agent, and are not bound to the real Dom node. Native Event objects are packaged inside React. Have the same interface as the browser native event, including stopPropagation() and preventDefault().

React Update queue

If there are multiple synchronized setstates (…) React will add their updates to the updateQueue queue in turn. When the update queue is processed in the render phase of the application, all the updates in the queue will be merged into one. The merging principle is that the update of the same property takes the last value. If there is an asynchronous setState(…) For asynchronous updates, follow the EventLoop principle for subsequent processing.

Update the React

// Source location: packages/react-reconciler/src/ReactUpdateQueue.js function createUpdate(expirationTime, SuspenseConfig) {var update = {expirationTime: expirationTime, suspenseConfig: expirationTime = expirationTime; suspenseConfig: expirationTime = expirationTime; SuspenseConfig, // tag Indicates the update type, such as UpdateState, ReplaceState, and ForceUpdate. Tag: UpdateState, // Update payload: Callback: null, // next update (task) next: null, // next side effect nextEffect: null}; Update.priority = getCurrentPriorityLevel(); update.priority = getCurrentPriorityLevel(); } return update; }Copy the code

Each update object has its own expirationTime, payload, priority, and reference to the next update. The priority of the current update is specified by the task system.

React Update queue

// Source location: Packages/react - the reconciler/SRC/ReactUpdateQueue js function createUpdateQueue (baseState) {var queue = {/ / the current state BaseState: baseState, firstUpdate: null, lastUpdate: Null, update firstCapturedUpdate of the first capture type in the queue: NULL, update lastCapturedUpdate of the first capture type in the queue: null, update lastCapturedUpdate of the first capture type in the queue: Null, // First side effect firstEffect: null, // Last side effect lastEffect: null, firstCapturedEffect: null, lastCapturedEffect: null}; return queue; }Copy the code

This is a unidirectional linked list structure.

When we use setState(), React creates an update object and adds it to the updateQueue queue by calling enqueueUpdate.

// Source location: Packages/react - the reconciler/SRC/ReactUpdateQueue js / / create the update into every setState updateQueue function enqueueUpdate (fiber, Update) {// Each Fiber node has its own updateQueue with an initial value of null, // fiber. Alternate refers to var alternate = for this node on the workInProgress tree fiber.alternate; var queue1 = void 0; var queue2 = void 0; If (alternate === null) {// alternate queue1 = fiber.updatequeue; queue2 = null; if (queue1 === null) { queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); } else {// if alternate is on the current tree and alternate is on the workInProgress tree queue1 = fibre.updatequeue; queue2 = alternate.updateQueue; If (queue1 === null) {if (queue2 === null) {if (queue2 === null) { Create queue queue1 = fiber. UpdateQueue = createUpdateQueue(fiber. MemoizedState); queue2 = alternate.updateQueue = createUpdateQueue(alternate.memoizedState); } else {// clone queue1 = fiber. UpdateQueue = cloneUpdateQueue(queue2); }} else {if (queue2 === null) {// if (queue2 === null) Queue2 = alternate. UpdateQueue = cloneUpdateQueue(queue1); } else {/ / if two nodes have updateQueue, does not need to deal with}}} the if (queue2 = = = null | | queue1 = = = queue2) {/ / after the above processing, Queue1 appendUpdateToQueue(queue1, update); } else {/ / after the above processing, if the two exist if the queue (queue1, lastUpdate = = = null | | queue2. LastUpdate = = = null) {/ / as long as there is a queue is not null, AppendUpdateToQueue (queuE1, update); appendUpdateToQueue(queue2, update); } else {// Add the update appendUpdateToQueue(queue1, update) only to queue1 because they are shared; // Still need to point lastUpdate to update queue2.lastUpdate = update; // Still need to point lastUpdate to update queue2.lastUpdate = update; }}... } function appendUpdateToQueue(queue, update) {if (queue.lastupdate === null) {// If the queue is empty, Queue.firstupdate = queue.lastUpdate = update; } else {// Add update to the end of the queue if the queue is not empty. Queue.lastupdate.next = update; queue.lastUpdate = update; }}Copy the code

In enqueueUpdate, React maintains two queue objects, queue1 and QueuE2, where queue1 is the latest queue in the current tree at the current Fiber node. Queue2 is the update queue at the Fiber node when the application was last updated (in the workInProgress tree), and the mutual logic between them looks like this.

  • Is that take queue1fiber.updateQueueSo, for queue2, we takefiber.alternate.updateQueue;
  • If both of them arenullThe callcreateUpdateQueue(...)Get the initial queue;
  • If one of the two isnullThe callcloneUpdateQueue(...)Obtain the queue from the peer;
  • If neither isnull, it willupdateAs alastUpdateAdd to queue1.

React processes the update queue

When the React application runs into the Render phase, it processes the update queue. The function that handles the update queue is processUpdateQueue

// Source location: packages/react-reconciler/src/ReactUpdateQueue.js function processUpdateQueue(workInProgress, queue, props, instance, renderExpirationTime) { ... Var update = queue.firstupdate; var resultState = newBaseState; // While (update! == null) { ... // If the first update is not empty, then the update queue is traversed. // getStateFromUpdate is used to merge the updates. ResultState = getStateFromUpdate(workInProgress, queue, update, resultState, props, instance); . update = update.next; }... / / set the current fiber node memoizedState workInProgress. MemoizedState = resultState; . } function getStateFromUpdate(workInProgress, queue, update, prevState, nextProps, instance) { switch (update.tag) { case UpdateState: { var _payload2 = update.payload; var partialState = void 0; If (typeof _payload2 === 'function') {// setState passes in parameter _payload2 of type function... partialState = _payload2.call(instance, prevState, nextProps); . } else {// setState is passed as the parameter _payload2 of type object partialState = _payload2; } // Merge the current state with the previous state. Return _assign({}, prevState, partialState); }}}Copy the code

The processUpdateQueue function is used to process the update queue. The processUpdateQueue function loops through the queue and merges the updates (objects) with update.next.

  • Call (instance, prevState, nextProps) payload2.call(instance, prevState, nextProps)
  • If the setState parameter is of type object, the update object can be obtained directly.
  • Finally, merge the two update objects by using object.assign () and return, taking the last value if the properties are the same.

React Page rendering

The Current tree is initialized during the PRERender phase when the React application first renders. The original Current tree has only one root node — a Fiber node of type HostRoot. The workInProgress tree is created in the later Render phase based on the current tree at this point. Perform a series of operations on the workInProgress tree (calculating updates, etc.) and pass the Effect List to the COMMIT phase. An update process is complete when the current tree is replaced by the workInProgress tree at the end of the COMMIT phase. Description:

  • React builds the workInProgress tree in the Render phase by relying on the current tree.
  • Do some update calculations in the workInProgress tree to get the Effect List.
  • After the list of side effects has been rendered to the page in the COMMIT phase, replace the current tree with the workInProgress tree (execute current = workInProgress).

The Current tree is the Fiber tree that corresponds to the application before the update, and the workInProgress tree is the Fiber tree that needs to update the screen.

Only one instance of the FiberRootNode constructor is the fiberRoot object. Each Fiber node is an instance of the FiberNode constructor, connected through the return, Child, and Sibling attributes to form a giant linked list. React calculates updates for each node on this list. What React does when updating the Fiber node tag is to assign a different value to the node’s effectTag attribute.

React hooks implementation principle

Borrow figure:

The react principle of hooks

React useCallback useMemo

These two apis, in fact, are conceptually easy to understand. One is “cache functions” and the other is “return values of functions”.

  • Within a component, methods that become dependencies of other UseEffects are recommended to be wrapped in useCallback, or written directly in the useEffect that references it.
  • If your function is passed as props, be sure to use the useCallback wrapper. It can be very annoying for a child component if it changes the function you pass every time it render. It’s also not good for React rendering optimization.
  • For scenarios where higher-order functions are used, useMemo is recommended

React captures the error lifecycle

  • componentDidCatch
  • static getDerivedStateFromError()

Why do I have to call the Hooks API at the top scope of function components?

  • A Hook API call generates a corresponding Hook instance (appending to the Hooks chain), but returns the state and the corresponding setter to the component. When re-render, the framework does not know which setters correspond to (unless you use HashMap to store them, This, however, requires that you pass the corresponding key to React, which adds complexity to the use of Hooks.
  • Re-render will reexecute the entire component from the first line of code, that is, the entire Hooks chain will be executed in order, because after the first render, only the corresponding Hooks’ memoizedState can be modified by the dispatch returned by useState. Therefore, you must ensure that the order of the Hooks is not changed, so you cannot call them from a branch. You must call them from the top level to ensure that they are executed in the correct order.

Once you have declared the hooks in a conditional statement, the next time the function component is updated, the hooks structure will be broken. MemoizedState in the current tree will cache the hooks information, which is inconsistent with the current workInProgress.

Summary: You can think of maintaining a state array and a cursor pointer. The first rendering pushes the current value into the States array and the pointer bound setter into the setters array. Each subsequent rendering resets the cursor position and reads the corresponding value from each array. Each setter will have a reference to the corresponding pointer position, so any call to the setter will be triggered to change the corresponding value in the state array. If you put it in a conditional statement. Then the order of setstates will be out of order.

      let first = true;
      const [num1, setNum1] = usestate(1);
      if (first) {
        const [num2, setNum2] = usestate(2);
        first = false
      }
      const [num3, setNum3] = usestate(3);

Copy the code

For the first rendering, when maintaining the States array [1,2,3], the setters array pointer points to [state[0],state[1],state[2]]. But when our component is updated, the states array is [1,2,3] and the setters array is [state[0],state[1]]. Obviously our setNum3 is set to 2.

React High-level components

  • A higher-order component is not a component; it is a pure function that transforms one component into another.
  • The main functions of higher-order components are code reuse and logical abstraction, abstraction and manipulation of state and props, refinement of components (such as adding life cycles), rendering hijacking, and so on. Reasonable use of higher-order components in real business scenarios can improve development efficiency and code maintainability.
  • The practicability of higher-order components makes them frequently used by a large number of react-js related third-party libraries, such as the connect method of React-Redux, react-loadable, etc. Understanding higher-order components is very helpful for us to understand the principles of various react-js third-party libraries 👍.
  • There are two ways to implement higher-order components, property brokering and reverse inheritance. It can be seen as an implementation of the decorator pattern in React: implementing enhancements to component functionality without modifying the original component.

React versus Vue

Vue React

  • Using Virtual DOM, has its own diff rendering algorithm
  • Reactive and Composable view components are provided.
  • Keep the focus on the core library and leave other functions such as routing and global state management to the related libraries.

1. The implementation principle of monitoring data changes is different

  • React the default was conducted by means of comparative reference, if not optimal (PureComponent/shouldComponentUpdate) may lead to a lot of unnecessary VDOM to render
  • Vue hijacks getters/setters and functions to know exactly what’s going on in the data and to achieve good performance without special optimizations

2. Differences in data flows

  • Vue is a two-way binding
  • React is a one-way data stream

3. Differences in component communication

4. Different rendering methods of templates

  • React is in component JS code, using native JS to implement common syntax in templates, such as interpolation, conditions, loops, etc., all implemented through JS syntax
  • Vue is implemented in a separate template from the component’s JS code through instructions, such as conditional statements that require V-if