React uses fiber’s dual cache mechanism to update the Fiber tree.

Build and Update

The paper

React maintains two Fiber trees and performs a COMMIT at the end of each scheduling task. Call scheduleUpdateOnFiber on mount or Update and decide to use synchronous or schedule asynchronous methods to perform updates on each node: Call the beginWork to create a new Fiber node and flag it (effectTag in earlier versions). Then collect a linked list of effectList in complete and execute commit replacement to complete the dom addition, deletion and modification.

Update the start

Reactdom.render (app,Element) executes the Render method on mount and creates a fiberRoot as the root to execute the scheduleUpdateOnFiber method. Update, enclosing setState method will use event priority to create the update object into a list in enqueueUpdate. Shared. Pending, then use scheduleUpdateOnFiber method

scheduleUpdateOnFiber

The mount or update scheduleUpdateOnFiber is the beginning of the build fiber tree, he will be based on the current priority lane use synchronized methods performSyncWorkOnRoot (start time for the current) to perform an update, Or use ensureRootIsScheduled to obtain the most urgent lane to be renewed. “> < span class =” ensureRootIsScheduled “> < span class =” ensureRootIsScheduled “> < span class =” ensureRootIsScheduled “>

ensureRootIsScheduled

This method gets NextLanes, which are the priority of the update task. Each time, lane is taken from NextLanes as the priority of the update task. Matching corresponding priority scheduling (scheduleCallback) call performSyncWorkOnRoot performConcurrentWorkOnRoot or direct implementation, in which executes workloop workloop points two kinds, One is to record the current workinprogress and commit it next time according to whether there is any remaining time to execute the task. The other is to execute the task synchronously

function workLoopConcurrent() { // Perform work until Scheduler asks us to yield while (workInProgress ! == null && ! shouldYield()) { performUnitOfWork(workInProgress); }}Copy the code

beginWork

The function of beginWork is mainly to execute effect, complete state update of class components, complete creation, diff, reuse of fiber nodes, and flag nodes with effect. Briefly after the HTML code is as follows, depending on the didReceiveUpdate decides to call bailoutOnAlreadyFinishedWork check subtree update logic decided to reuse or then traverse down completely

If it is not reusable, the reconcileChildren method is finally called to compare fiber with reactElement according to the reconcileChildren tag to complete the diff, and the subfiber node is returned to continue the beginWork until the completeWork is iterated to the leaf node.

function beginWork(current, workInProgress, RenderLanes) {if(workinprogress._debugNeedsremount && current! == null) return remountFiber(...) // For FunctionComponent, FunctionComponent, ClassComponent, FunctionComponent, FunctionComponent, FunctionComponent, FunctionComponent, FunctionComponent, ClassComponent, FunctionComponent, FunctionComponent, FunctionComponent, FunctionComponent, FunctionComponent Class, and for HostComponent, DOM node tagName if (oldProps! == newProps || hasContextChanged() || ( workInProgress.type ! == current.type )) { didReceiveUpdate = true; } }else if (! includesSomeLane(renderLanes, updateLanes)) { ..... }... switch(tag){ case FunctionComponent: ... return updateFunctionComponent(current, workInProgress, _Component, resolvedProps, renderLanes); case ClassComponent: ... return updateClassComponent(current, workInProgress, _Component2, _resolvedProps, renderLanes); . }Copy the code

The diff logic

In general, there are four operations for the reconcileChildFibers that add, delete, change and remove the reconcileChildFibers, which perform different functions based on the number of children

For a single node

React Diff decides whether to reuse the reconcileSingleElement by determining the key and type. The specific implementation functions are in the reconcileSingleElement, and the code is not attached.

For multiple nodes

React traverses through two rounds. In the first round, childrens[I] checks whether positive [I] and Fiber key are the same. If positive [I] and Fiber key are different, the loop interrupts the traversal and updates the key and tag. When childrens and Fiber are not traversed, newChildren traverses the remaining nodes and compares the lastPlacedIndex with the children[I] position (lastPlacedIndex is the maximum position of the previously multiplexed node in oldFiber chain). If I is big and doesn’t move, small moves back and updates the position of the lastPlacedIndex. NewChildren C in oldFiber is 2, update lastPlacedIndex in oldFiber is 2, update lastPlacedIndex in oldFiber is 0, Move it compared to lastPlacedIndex, same with B, E doesn’t move and updates lastPlacedIndex, D moves

function reconcileChildrenArray( returnFiber: Fiber, currentFirstChild: Fiber | null, newChildren: Array<*>, lanes: Lanes, ): Fiber | null {/ * * returnFiber: currentFirstChild parent Fiber node * currentFirstChild: current WIP update task node * newChildren (Fiber) : */ / resultingFirstChild is the first fiber in the new Fiber list after diff. let resultingFirstChild: Fiber | null = null; // resultingFirstChild is the first fiber in the new linked list. / / previousNewFiber used to subsequent new fiber after receiving the first fiber let previousNewFiber: fiber | null = null; OldFiber = currentFirstChild; oldFiber = currentFirstChild; // let lastPlacedIndex = 0; // store the index of the new node traversed let newIdx = 0; // Let nextOldFiber = null; // This loop processes node updates, depending on whether the node is reusable to decide whether to interrupt traversal for (; oldFiber ! == null && newIdx < newChildren.length; NewIdx ++) {if (oldFibre. index > newIdx) {nextOldFiber = oldFiber; oldFiber = null; NextOldFiber = oldFive.sibling; // nextOldFiber = oldFive.sibling; // oldFiber will be reused only if the key and tag are the same for DOM elements. Null const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], lanes,); // If newFiber is null, the node cannot be reused because the key or tag is different. If (newFiber === null) {if (oldFiber === null) {// oldFiber is null. D is the new node // old A-b-C // new A-B-C-D oldFiber = nextOldFiber; } break; } if (shouldTrackSideEffects) {// shouldTrackSideEffects is true if (oldFiber &&newFiber. Alternate === null) {// NewFiber. Alternate = oldFiber. Alternate // oldFiber is the WIP node, whose alternate is the current node // oldFiber exists, In addition, the new fiber node after the update does not have a current node, which means that there will not be a current node displayed on the screen after the update, and the WIP // node after the update will be called the current node. Therefore, delete the existing WIP node deleteChild(returnFiber, oldFiber); LastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); If (previousNewFiber === null) {resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; // Move oldFiber = nextOldFiber in sync with newChildren's traversal; } // Handle node deletion. If (newIdx === newchildren.length) {// If (newIdx == newchildren.length) { Delete the remaining nodes in oldFiber chain deleteRemainingChildren(returnFiber, oldFiber); return resultingFirstChild; } // Process the new node. If (oldFiber === null) {for (; oldFiber == null) {for (; newIdx < newChildren.length; Const newFiber = createChild(returnFiber, newChildren[newIdx], lanes); if (newFiber === null) { continue; LastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); If (previousNewFiber === null) {resultingFirstChild = newFiber; if (previousNewFiber === null) {resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; } return resultingFirstChild; } // When creating a new fiber node based on oldFiber node, insert the remaining old children into a map with oldFiber node as the key. OldFiber const existingChildren = mapRemainingChildren(returnFiber, oldFiber); // Node move for (; newIdx < newChildren.length; NewIdx ++) {const newFiber = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx], lanes, ); if (newFiber ! == null) { if (shouldTrackSideEffects) { if (newFiber.alternate ! // If newFiber alternate is not null, newFiber is not new. // If newFiber alternate is null, newFiber is not new. OldFiber existingChildren.delete(newFiber.key === null) from the map ? newIdx : newFiber.key, ); LastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); If (previousNewFiber === null) {resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; }} if (shouldTrackSideEffects) {// If (shouldTrackSideEffects) { OldFiber existingChildren.forEach(child => deleteChild(returnFiber, child)); } return resultingFirstChild; }Copy the code

completeWork

CompleteWork main

  1. Collect effectlist
  2. Manipulate the DOM on the hostComponent
  3. Dom property handling
  4. Throw exception node

To be continued…