React source code

The purpose of this chapter is to take a look at the React source architecture and various modules.

Before learning the actual code, we need to have a map of the React source code in our head. We need to know the general process and framework of rendering React, so that we can see how react updates from the perspective of God.

React core can be expressed as UI =fn(state)

const state = reconcile(update);
const UI = commit(state);
Copy the code

Fn above can be divided into the following parts:

  • Scheduler: Reconciliates priorities by reconciliating higher-priority tasks first
  • Reconciler: Find out which nodes are changed and put different Flags on them (the old version of React was called Tag).
  • Renderer: To render the Reconciler’s labeled nodes onto the view

A picture is worth a thousand words:

jsx

JSX is an extension of the javascript language. React converts JSX to react. createElement using the Babel syntax. The React. CreateElement method returns a virtual-DOM object. All JSX is essentially a syntactic sugar for the React. In Chapter 5, JSX, we’ll cover the results of JSX parsing in more detail.

Fiber double cache

The Fiber object contains the attributes, types, and DOM of the node. Fiber forms the Fiber tree through child, Sibling, and return. It also contains the updateQueue used to calculate the state when updating the state. UpdateQueue is a linked list structure that may have multiple uncounted updates, and an update is a data structure that contains the updated data, priority, and so on, along with information about side effects.

The current Fiber tree describes the dom tree that is currently presented. The workInProgress Fiber is the Fiber tree that is being updated. Both Fiber trees are running in memory. After the workInProgress Fiber is built, it will be applied to the DOM as the Current Fiber

On mount (first render), a Fiber object is constructed from the JSX object (Class Component or the return value of the render operator Function Component) to form a Fiber tree, Then this Fiber tree will be applied to the real DOM as the Current Fiber. During update (state update such as setState), the JSX object after state change will be compared with the current Fiber to form a new workInProgress Fiber. The workInProgress Fiber is then switched to current Fiber and applied to the real DOM for the purpose of updating, all in memory, reducing the need to manipulate the DOM’s good performance.

For example, the Fiber dual-cache structure of the following code is described in detail in Chapter 7

function App() {
  const [count, setCount] = useState(0);
  return (
   	<>
      <h1
    		onClick={()= > {
          // debugger;
          setCount(() => count + 1);
        }}
    	>
 					<p title={count}>{count}</p> xiaochen
      </h1>
    </>
  )
}

ReactDOM.render(<App />.document.getElementById("root"));
Copy the code

scheduler

Scheduler is used to schedule tasks. There is no Scheduler part in React15, so tasks are not prioritized and cannot be interrupted.

We know that asynchronous interruptible updates require the browser to specify a time and then suspend the task if there is no time left. RequestIdleCallback seems to be a good choice, but it has compatibility and trigger instability reasons and is implemented using MessageChannel in Act17.

//ReactFiberWorkLoop.old.js
function workLoopConcurrent() {
  while(workInProgress ! = =null && !shouldYield()) {//shouldYield determines whether to pause the taskworkInProgress = performUnitOfWork(workInProgress); }}Copy the code

The priority of each task in Scheduler is expressed by expiration time. If the expiration time of a task is close to the present, it means that it will soon expire and has a high priority. If the expiration time is long, it has a low priority. Expired tasks are stored in a taskQueue. Timerqueues and TimerQueues are small top stacks, so Peek takes out the most recent task with the highest priority and executes it first.

Lane model

React earlier versions used the expirationTime attribute to represent a priority, which would not work well with an I/O. Now Lane is a more granular priority representation. The same binary number can have more than one bit of the same priority, which can represent the concept of ‘batch’, and binary is easy to calculate.

This is like a racing game, at the beginning of the game will be assigned a track, race after everyone will rob inner ring circuit (react is rob priority Lane), the end of the game, and finally a car if left behind a lot, it will run to the inner ring circuit, finally reached their destination (corresponding to the react is hunger, A low-priority task becomes a high-priority task if it is continuously interrupted by a high-priority task when it expires.)

The binary bits of a Lane are as follows. The more bits of 1, the lower the priority

//ReactFiberLane.js
export const NoLanes: Lanes = / * * / 0b0000000000000000000000000000000;
export const NoLane: Lane = / * * / 0b0000000000000000000000000000000;

export const SyncLane: Lane = / * * / 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = / * * / 0b0000000000000000000000000000010;

export const InputDiscreteHydrationLane: Lane = / * * / 0b0000000000000000000000000000100;
const InputDiscreteLanes: Lanes = / * * / 0b0000000000000000000000000011000;

const InputContinuousHydrationLane: Lane = / * * / 0b0000000000000000000000000100000;
const InputContinuousLanes: Lanes = / * * / 0b0000000000000000000000011000000;

export const DefaultHydrationLane: Lane = / * * / 0b0000000000000000000000100000000;
export const DefaultLanes: Lanes = / * * / 0b0000000000000000000111000000000;

const TransitionHydrationLane: Lane = / * * / 0b0000000000000000001000000000000;
const TransitionLanes: Lanes = / * * / 0b0000000001111111110000000000000;

const RetryLanes: Lanes = / * * / 0b0000011110000000000000000000000;

export const SomeRetryLane: Lanes = / * * / 0b0000010000000000000000000000000;

export const SelectiveHydrationLane: Lane = / * * / 0b0000100000000000000000000000000;

const NonIdleLanes = / * * / 0b0000111111111111111111111111111;

export const IdleHydrationLane: Lane = / * * / 0b0001000000000000000000000000000;
const IdleLanes: Lanes = / * * / 0b0110000000000000000000000000000;

export const OffscreenLane: Lane = / * * / 0b1000000000000000000000000000000;
Copy the code

Reconciler (Render Phase)

The Reconciler occurs in the Render phase, which performs the beginWork and completeWork (more on that later) for each node, or calculates the state, compares the differences between the nodes, and assigns the corresponding effectFlags to the nodes (which correspond to dom node additions, subtractions, and changes).

The Reconciler works in the Render phase, which in a nutshell creates or updates Fiber nodes. During mount, Fiber object will be generated according to JSX. During update, workInProgress Fiber tree will be constructed according to the comparison between JSX object formed by the latest state and current Fiber tree. The comparison process is called diff algorithm.

The diff algorithm, which occurs in the reconcileChildFibers function in the Render phase, is divided into single-node DIff and multi-node DIFF (for example, a single node containing multiple child nodes is a multi-node DIFF). Props, etc. to determine whether a node is reused or directly created. Multi-node DIff involves the addition and deletion of nodes and the change of node positions. For details, see Chapter 9.

Flags are marked on these fibers when reconciliating. These Flags are applied to the real DOM during the COMMIT phase. These Flags represent additions, deletions, and changes to nodes, such as

//ReactFiberFlags.js
export const Placement = / * * / 0b0000000000010;
export const Update = / * * / 0b0000000000100;
export const PlacementAndUpdate = / * * / 0b0000000000110;
export const Deletion = / * * / 0b0000000001000;
Copy the code

The render stage traverses the Fiber tree similar to the DFS process. The ‘capture’ stage occurs in the beginWork function, which mainly creates Fiber nodes and calculates state and diff algorithm. The ‘bubble’ stage occurs in the completeWork, which mainly does some finishing work. For example, processes props for a node, and forms an effectList that is a linked list of nodes marked for updates

The depth-first traversal process is as follows. The numbers in the figure are sequential and return points to the parent node. Chapter 9 explains this in detail

function App() {
  return (
   	<>
      <h1>
        <p>count</p> xiaochen
      </h1>
    </>)}Copy the code

Look at the following code

function App() {
  const [count, setCount] = useState(0);
  return (
   	 <>
      <h1
        onClick={()= > {
          setCount(() => count + 1);
        }}
      >
        <p title={count}>{count}</p> xiaochen
      </h1>
    </>)}Copy the code

If p and H1 nodes are updated, the effectList is as follows: rootFiber-> H1 -> P, by the way, fiberRoot is the root node of the entire project, there is only one, rootFiber is the root node of the application, there may be multiple. For example, multiple reactdom.render (
, document.getelementByid (“root”)); Create multiple application nodes

Renderer (Commit Phase)

Renderer occurs during the COMMIT phase, which traverses the effectList to perform corresponding DOM operations or parts of the lifecycle.

Renderers work in the Commit phase, which iterates through the effectList generated in the Render phase and performs operations on real DOM nodes and life cycles. Renderers vary from platform to platform, such as the React-DOM for browsers.

The COMMIT phase occurs in the commitRoot function, which iterates through the effectList, using three functions to process nodes on the effectList, These three functions are commitBeforeMutationEffects, commitMutationEffects, commitLayoutEffects, they mainly do the following, later in detail, and now have a structure in the brain

concurrent

It is a collection of features (fiber, Schduler, Lane, Suspense) that aim to improve application response speed and make CPU-intensive updates of applications less lag. At its core, it implements a set of asynchronous interruptable, prioritised updates.

As we know, the average FPS of a browser is 60Hz, which means that the browser will refresh every 16.6ms. The JS execution thread and GUI, i.e. the drawing of the browser, are mutually exclusive, because JS can manipulate dom and affect the final rendering result. Therefore, if js execution takes too long, the browser will have no time to draw DOM, resulting in lag. React17 allocates a chunk of time (time slice) for each frame to execute. If the js is not finished within this time, it pauses execution and waits for the next frame to execute, handing execution back to the browser to draw.

Compare the difference between enabling concurrent mode and not enabling concurrent mode. After enabling concurrent mode, the task execution of building Fiber is not blocked all the time, but divided into task after task

Did not open the concurrent

Open the concurrent