This is the first day of my participation in the August Challenge. For details, see:August is more challenging

Recently, I am learning the React Fiber architecture related content. It is really difficult to learn the source code. I just saw the React technology revealed

Of course, one time is far from enough, after the time also need to brush several times, and then follow the demo to try more. I believe that reading a book a hundred times reveals its meaning

React idea: Respond quickly

Two constraints:

  • CPU bottleneck: When a large amount of computing or device performance is insufficient, the page loses frames, resulting in a lag.
  • I/O bottleneck: After a network request is sent, it cannot respond quickly because it needs to wait for the data to be returned.

Solution:

  • Time slice, long tasks are broken down into each frame. Set aside some time (5ms) for the JS thread per frame in the browser,ReactUse that time to update components, and when the time reserved is insufficient,ReactGiving control of the thread back to the browser gives it time to render the UIReactWait for the next frame of time to continue the interrupted work.
  • In Suspense, code and data are loaded synchronously, with React iteratively rendering components that require data as data is received until everything is rendered.

New and old React architecture

  1. The React15 old architecture is synchronous and can be divided into two layers:
  • Reconciler — The component responsible for finding change
  • Renderer – Responsible for rendering the changed components to the page

The Reconciler and Renderer work alternately, and the process is synchronous, and the Reconciler determines updates through recursive subcomponents, which can take up a lot of thread time when the hierarchy is deep and cause pages to get stuck.

  1. The new React16 architecture is asynchronous interruptible and can be divided into three layers:
  • Scheduler — Schedules the priority of tasks, with higher-priority tasks going to the Reconciler first
  • Reconciler — Responsible for identifying the components of change that are adopted internallyFiberThe architecture of the
  • Renderer – Responsible for rendering the changed components to the page

After the Scheduler gives the task to the Reconciler, it labels the changing virtual DOM with symbols representing add/delete/update. Only when all components have completed the Reconciler’s work is it uniformly assigned to the Renderer. The Renderer synchronously performs DOM operations based on the markup made by the Reconciler for the virtual DOM.

Both Scheduler and Reconciler work in memory, do not update the DOM on the page, and can be repeatedly interrupted by: 1. Other higher-priority tasks need to be updated first; 2. The current frame has no remaining time.

The Fiber structure

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // ------ as a static data structure attribute ------
  // Each 'Fiber node' corresponds to a 'React Element', which holds the component type (function component/class component/native component...). , and corresponding DOM nodes

  this.tag = tag;           Function/Class/Host...
  this.key = key;           / / the key attribute
  this.elementType = null;  // In most cases, the same as type. In some cases, FunctionComponent uses the React. Memo package
  this.type = null;         // For FunctionComponent, it refers to the function itself; for ClassComponent, it refers to the class; for HostComponent, it refers to the DOM node tagName
  this.stateNode = null;    // The actual DOM node corresponding to Fiber

  // ------ as a schema attribute ------
  // Connect other Fiber nodes to form a Fiber tree
  this.return = null;  // Point to the parent Fiber node
  this.child = null;   // Point to the subfiber node
  this.sibling = null; // Point to the first sibling Fiber node on the right
  this.index = 0;

  this.ref = null;

  // ------ as a dynamic unit of work attribute ------
  // Save the status of the component changed, the work to be performed (need to be deleted/inserted/updated...)
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;          // Save props for the update phase
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;
  
  // Save the DOM operation caused by this update
  this.effectTag = NoEffect;
  this.nextEffect = null;

  this.firstEffect = null;  // The first Fiber in the effectList
  this.lastEffect = null;   // The last Fiber node in the effectList

  // Scheduling priority is related
  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  // Point to the corresponding fiber from the next update
  this.alternate = null;
}
Copy the code

Working principle of Fiber

React uses a “dual cache” to build and replace the Fiber tree — which corresponds to creating and updating the DOM tree.

The technique of building in memory and replacing directly is called double caching

The Fiber tree corresponding to the content currently displayed on the screen is called the Current Fiber tree, and the Fiber tree being built in memory is called the workInProgress Fiber tree.

The Fiber node in the Current Fiber tree is called current Fiber and the Fiber node in the workInProgress Fiber tree is called workInProgress Fiber. They are connected through alternate properties.

There is only one root node for the entire application, the fiberRootNode. We can call ReactDOM. Render multiple times to render different component trees, which will have different Rootfibers. The fiberRootNode’s Current points to the Fiber tree for the rendered content on the current page, the Current Fiber tree.

Diff occurs during the creation of the workInProgress fiber, which can reuse the node data corresponding to the current Fiber tree or create new ones.

In-depth understanding of JSX

  • JSXAt compile timeBabelCompiled intoReact.createElementMethods;
  • React.createElementWill eventually callReactElementMethod returns an object that contains component data and takes one argument$$typeof: REACT_ELEMENT_TYPEMarked that the object is aReact Element;
  • We often useClassComponentwithFunctionComponentBuild the component and pass it as the first parameterReact.createElement;
  • In the componentmountWhen,ReconcilerAccording to theJSXThe component content described is generated for the componentFiber nodeIn theupdateWhen,ReconcilerwillJSXwithFiber nodeThe saved data is compared, and the corresponding components are generatedFiber node, and according to the comparison resultsFiber nodeIn the playtag;
  • Fiber nodeContains more information:
    • The component is being updatedpriority
    • The component’sstate
    • The component is marked forRendererthetag

The Fiber tree creation process

Render phase

The depth-first traversal starts with rootFiber. The “pass” phase calls the beginWork method for each Fiber node traversed. This method creates a sub-fiber node based on the incoming Fiber node and connects the two Fiber nodes. The “home” phase calls completeWork to process the Fiber node. The “recursion” and “return” phases are interlaced until the “return” to rootFiber.

The work of the Render phase is done in memory, and the Renderer is notified of the DOM operations that need to be performed when the work is done. The specific types of DOM operations to be performed are stored in fiber.effectTag.

Recursive calls beginWork

Main work: Create the sub-fiber node and mark the effectTag

function beginWork( current: Fiber | null, / / the current component corresponding ` ` Fiber node in the last update ` Fiber node `, namely ` workInProgress. Alternate `, namely the current rendering node workInProgress page: Fiber, / / the current component corresponding ` ` Fiber node, the node renderLanes in memory: Lanes, / / priority) : Fiber | null {the if (current! == null) {// current === null? 'to distinguish between' mount 'and' update '// updatea, if' didReceiveUpdate === false ' // 1. 'props' == newProps && workinprogress.type === current. Type', that is, 'props' and' Fiber. IncludesSomeLane (renderLanes, updateLanes) ', i.e. current 'Fiber node' priority is not enough} else {didReceiveUpdate = false; } // Create different sub-fiber nodes based on the fiber.tag, which ultimately enters the reconcileChildren method. switch (workInProgress.tag) { case IndeterminateComponent: // ... Case LazyComponent: //... Case FunctionComponent: //... Case ClassComponent: //... Case HostRoot: //... Case HostComponent: //... Case HostText: //... Omit / /... Omit other types}}Copy the code
function reconcileChildren( current: Fiber | null, workInProgress: Fiber, nextChildren: any, renderLanes: If (current === null) {// if (current === null) {else {// If (current === null)} else {// If (current === null)} else {// If (current == null)} else {// if (current == null)} He compares the current component to the last 'Fiber node' of the component (also known as' Diff 'algorithm) and generates the new' Fiber node '}}Copy the code

By calling completeWork

In the update case, there is already a DOM node corresponding to the Fiber node, so there is no need to generate a DOM node. The main thing you need to do is handle props. For example:

  • onClick,onChangeEtc callback function registration
  • To deal withstyle prop
  • To deal withDANGEROUSLY_SET_INNER_HTML prop
  • To deal withchildren prop

In the mount case, the main logic consists of three:

  • forFiber nodeGenerate correspondingDOM node
  • The sons ofDOM nodeI’m going to insert what I just generatedDOM nodeIn the
  • withupdateSimilar processing in logicpropsThe process of

Note:

  1. updateWhen,updateHostComponentInside, it’s donepropsWill be assigned toworkInProgress.updateQueue.
  2. mountWhen onlyrootFiberThere arePlacement effectTagThrough theappendAllChildrenMethod to insert the entire DOM tree.
  3. After each executioncompleteWorkAnd there areeffectTagtheFiber nodeWill be appended toeffectListIn, finally form a torootFiber.firstEffectA unidirectional linked list for the starting point.The commit phaseYou just have to iterateeffectListI can execute all of themeffect.
  4. effectSide effects include: insertionDOM node(Placement) and updateDOM node(Update) and deleteDOM node(Deletion) anduseEffectoruseLayoutEffectOperations related to.

The commit phase

The commitRoot method is the starting point for work in the COMMIT phase. FiberRootNode will be used as a transmission parameter.

The COMMIT phase can be divided into three sub-phases, each of which iterates through the effectList once:

  • Before mutation phase (executionDOMBefore the operation)
    1. To deal withDOM nodeAfter rendering/deletionautoFocus,blurlogic
    2. callgetSnapshotBeforeUpdateLifecycle hook
    3. schedulinguseEffect
  • The mutation phase (executionDOMThe operation is performedcommitMutationEffects
    1. According to theContentReset effectTagResetting a text node
    2. updateref
    3. According to theeffectTagRespectively, whereeffectTagIncluding,Placement | Update | Deletion | Hydrating)
    4. For FunctionComponent,Mutation stagesWill performuseLayoutEffectThe destruction function of.
    5. For the HostComponent,Mutation stageswillupdateQueue(Render phase generation) the corresponding content is rendered on the page.
  • Layout phase (executionDOMAfter the operation)
    1. callLifecycle hookandhookThe relevant operation
    2. Assignment ref
    3. switchfiberRootNodePoint to theThe current Fiber treeinMutation stagesAfter the end,Layout stagePrior to the start
    4. componentWillUnmountWill be inMutation stagesThe execution. At this timeThe current Fiber treeIt also points to the previous updateFiber tree, within the lifecycle hookDOMBefore the update.
    5. componentDidMountandcomponentDidUpdateWill be inLayout stageThe execution. At this timeThe current Fiber treeIt has been pointed to the updatedFiber tree, within the lifecycle hookDOMIt’s updated.

reference

  1. React Technology revealed