react source code analysis 3.react source code architecture

react source code analysis 3.react source code architecture

Video course (efficient learning): Enter the course

The purpose of this chapter is to let us understand the react source code architecture and various modules.

Before real code learning, we need to have a map of react source code in our brain and know the general process and framework of react rendering, so as to see how react is updated from the perspective of God. Come on, boy.

The core of react can be represented by ui=fn(state). For more details, you can use

const state = reconcile(update);
const UI = commit(state);

The above fn can be divided into the following parts:

  • Scheduler: sort the priority, and let the tasks with high priority reconcile first
  • Reconciler: find out which nodes have changed and mark them with different Flags (the old version of react is called Tag)
  • Renderer: renders the labeled nodes in the Reconciler onto the view

A picture is worth a thousand words:

jsx

jsx is an extension of js language. React converts jsx into React.createElement through babel lexical parsing (refer to babel related plug-ins for specific conversion). The React.createElement method returns the virtual dom object (the object used to describe the dom stage in memory). All jsx is essentially the syntax sugar of React.createElement, It can declaratively write what ui effect we want the component to present. In Chapter 5, we will introduce the results of jsx parsing in detail.

Fiber dual cache

The fiber object stores the attributes, types, DOMS, etc. of this node. Fiber forms a fiber tree through child, sibling and return (pointing to the parent node). It also stores the updateQueue used to calculate the state when updating the status. updateQueue is a linked list structure. There may be multiple uncomputed updates on it. update is also a data structure, It contains updated data, priorities, etc. in addition to these, it also contains information related to side effects.

Double cache means that there are two Fiber trees. The current Fiber tree describes the DOM tree currently presented, and the workInProgress Fiber is the Fiber tree being updated. Both Fiber trees are running in memory. After the workInProgress Fiber is built, it will be used as the current Fiber on the dom

During mount (the first rendering), the Fiber object will be constructed according to the jsx object (the return value of the Class Component or the render Function Component of the function) to form a Fiber tree, and then the Fiber tree will be applied to the real dom as current Fiber. During update (state update, such as setState), A new workInProgress Fiber is formed by comparing the jsx object with the current Fiber after the state change, and then the workInProgress Fiber is switched to current Fiber, which is applied to the real dom to achieve the purpose of updating, and all this occurs in memory, thus reducing the operation of good dom performance.

For example, the following code Fiber The dual cache structure is as follows, which will be explained 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"));

scheduler

The Scheduler is used to schedule tasks. react15 does not have a Scheduler, so all tasks have no priority and cannot be interrupted. They can only be executed synchronously.

We know that to implement asynchronous and interruptible updates, the browser needs to specify a time. If there is no time left, the task needs to be suspended. requestIdleCallback seems to be a good choice, but it has reasons for compatibility and unstable triggering. MessageChannel is used in react17.

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

The priority of each task in the Scheduler is expressed by the expiration time. If the expiration time of a task is very close to the present, it means that it will expire soon and the priority is very high. If the expiration time is very long, its priority is low. Tasks that do not expire are stored in timerQueue and expired tasks are stored in taskQueue, timerQueue and timerQueue are small top heap, so peek takes out the task closest to the current time, that is, the task with the highest priority, and then executes it first.

Lane model

In versions before react, the expirationTime attribute was used to represent the priority. This priority cannot work well with IO (Io's priority is higher than cpu's priority). Now there is a more fine-grained priority representation method lane. Lane uses binary bits to represent the priority, 1 in binary bits to represent the position, and the same binary number can have multiple bits of the same priority, This can represent the concept of 'batch', and binary is easy to calculate.

This is like a car race. A track will be assigned at the beginning of the race. After the race starts, everyone will grab the track in the inner circle (Lane with high priority in react). At the end of the race, if the last car falls behind a lot, it will also run to the track in the inner circle and finally reach the destination (corresponding to the hunger problem in react, if a low priority task is interrupted by a high priority task, it will become a high priority task at its expiration time.)

The binary bits of 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;

reconciler (render phase)

Reconciler occurs in the render phase. In the render phase, beginWork and completeWork will be performed for the node respectively (which will be described later), or state will be calculated to compare the differences between nodes and assign corresponding effectFlags to the node (corresponding to the addition, deletion and modification of dom nodes)

The Coordinator works in the render phase. In a word, the Reconciler will create or update the Fiber node. During mount, the Fiber object will be generated according to jsx, and during update, the workInProgress Fiber tree will be constructed according to the comparison between the jsx object formed by the latest state and the current Fiber tree. This comparison process is the diff algorithm.

The diff algorithm occurs in the reconcileChildFibers function in the render stage. The diff algorithm is divided into single node diff and multi node diff (for example, if a node contains multiple child nodes, it belongs to multi node diff) , a single node will judge whether to reuse or directly create a new node according to the key, type and props of the node. Multi node diff will involve the addition and deletion of nodes and the change of node location. See Chapter 9 for details.

When reconciling, Flags tags will be marked on these fibers. In the commit phase, these tags will be applied to the real dom. These tags represent the addition, deletion and modification of nodes, such as

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

The render phase traverses the Fiber tree in a process similar to dfs. The capture phase occurs in the beginWork function. The main work of this function is to create Fiber nodes and calculate state and diff algorithms. The bubble phase occurs in completeWork. This function mainly does some finishing work, such as processing the props of nodes and forming a linked list of effectList, which is marked Linked list formed by updated nodes

The depth first traversal process is as follows. The numbers in the figure are in order, and return points to the parent node. It is explained in detail in Chapter 9

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

See the following code

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

If the p and h1 nodes are updated, the effectList is as follows. From rootFiber - > h1 - > p, incidentally, the fiberRoot is the root node of the whole project, and there is only one. rootFiber is the root node of the application, and there may be multiple, such as multiple reactdom.render (< app / >, document.getelementbyid ("root"); Create multiple application nodes

renderer(commit phase)

The Renderer occurs in the commit phase, which traverses the effectList and performs the corresponding dom operations or part of the life cycle.

The Renderer works in the commit phase. The commit phase will traverse the effectList formed in the render phase and perform the operations and some life cycles of real DOM nodes. The renderers corresponding to different platforms are different. For example, the browser corresponds to react dom.

The commit phase takes place in the commitRoot function. This function mainly traverses the effectList and uses three functions to process the nodes on the effectList. These three functions are commitbeforemultivationeffects, commitMutationEffects and commitLayoutEffects. Their main tasks are as follows, which will be explained in detail later. Now there is a structure in the brain

concurrent

It is a collection of functions (such as fiber, scheduler, lane and suspend). Its purpose is to improve the response speed of applications and make cpu intensive updates less stuck. Its core is to realize a set of asynchronous, interruptible and priority updates.

We know that the fps of the general browser is 60Hz, that is, it will be refreshed every 16.6ms, while the js execution thread and the GUI, that is, the drawing of the browser, are mutually exclusive, because js can operate the dom and affect the final rendering results. Therefore, if the js execution time is too long, the browser will not have time to draw the dom, resulting in jamming. react17 will allocate a time (time slice) to js for execution in each frame. If js has not finished executing within this time, it is necessary to pause its execution, wait for the next frame to continue, and return the execution right to the browser for drawing.

Compare the difference between enabling and not enabling concurrent mode. After enabling, the execution of the task of building Fiber will not be blocked all the time, but will be divided into tasks one by one

concurrent is not turned on

Enable concurrent

Added by Stuie_b on Mon, 29 Nov 2021 06:29:17 +0200