directory
After reconcileChildFibers reconcileSingleElement reconcileSingleTextNode reconcileChildFibers reconcileSingleElement reconcileSingleTextNode ReconcileChildrenArray Tool function summary lastPlacedIndexCopy the code
Here we use React 17.0.3 as an example.
A, Fiber
React introduced Fiber starting with 16.
Before Fiber, React made a “big move” when it decided to load or update the component tree. This action includes life cycle calls, diff procedure calculations, DOM tree updates, and so on. This action is large, takes a long time, and it happens synchronously. Once started, it cannot be interrupted. This means you can’t do anything until the mount/update is complete.
Faced with the problem of “single task takes too long”, the solution is to divide a huge task into N small tasks (as shown below).
Each tiny task is called Fiber, and it represents one unit of work, the smallest unit of work that receives dispatch.
Each of the peaks in the figure above and in between means a unit of work (Fiber). Each time the peak is reached, it means that the task has surrendered its occupation of the main thread.
Every time you finish a small task, you pause the main thread to see if there is a higher priority that needs to be addressed. This ensures that the main thread is always doing what it should be doing at the moment.
This new reconciliation is called Fiber Reconciler.
Ii. Implementation process
React React React ReactClick here to see)
There are three stages: mount, update and unmount
mount
When a component instance is created and inserted into the DOM
The lifecycle functions are called in the following order:
constructor()
: initializes state or method bindingstatic getDerivedStateFromProps()
: Triggered before each render, not often usedrender()
Check for changes to this.props and this.state and make different render strategies depending on the return valuecomponentDidMount()
: immediately after the component is mounted (inserted into the DOM tree)
update
An update is triggered when a component’s props or state changes
The lifecycle functions are called in the following order:
static getDerivedStateFromProps()
: same as aboveshouldComponentUpdate()
: Determines whether to render the changes of state or props based on the returned value. This parameter is used for performance optimizationrender()
: same as abovegetSnapshotBeforeUpdate()
: called before the last render output (Commit to the DOM node), the pre-commit phase in the figure, not commonly usedcomponentDidUpdate()
: Is invoked immediately after the update is complete
uninstall
The following method is called when a component is removed from the DOM:
ComponentWillUnmount () : Called directly before components are unmounted and destroyed
Three, Dom diff
strategy
The traditional DIFF algorithm with O(n^3) time complexity obviously cannot meet the performance requirements of the framework. Therefore, the React team proposed three hypotheses based on the characteristics of the front-end interface:
-
The same component has the same DOM structure, and different components have different DOM structures (Component diff)
-
A group of child nodes at the same level that can be distinguished by a unique ID (Element diff)
-
In THE DOM structure, node operations across the hierarchy are very few and can be ignored (tree diff).
Reduce the time complexity of the DIff algorithm from O(n^3) to O(n) based on these three assumptions
Update logic
In React, Fiber acts as a virtual node
Here, some attributes are captured
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key; / / key values
this.elementType = null;
this.type = null;
this.stateNode = null;
// Fiber
this.return = null;
this.child = null; / / child nodes
this.sibling = null; // Sibling nodes
this.index = 0; // Node subscript.this.alternate = null; . }Copy the code
So let’s see how we update our view, right
Starting from the root node:
div
The node finds the node through the child attributediv1
div1
The node finds the node through the sibing attributeul
ul
The node finds the node through the child attributeli
li
A node is compared with the node information stored in its alternate property. After the comparison is complete, update commit3 is submitted to the node via returnul
nodeul
A node is compared with the node information stored in its alternate property. After the comparison is complete, commit2 is generated, together withcommit3
Along with the return todiv
nodediv1
A node is compared with the node information stored in its alternate property. After the comparison is complete, update commit1 is submitted to the node via returndiv
node- After all updates are retrieved (Commit1-3), they are updated again into the real DOM
Source code analysis
Instead of looking up the code step by step, find the react\ Packages \react- Reconciler \ SRC \ reactChildFibre.new.js file.
reconcileChildFibers
The method is used to judge node types and different harmonic functions are used to deal with nodes for different types
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any, // New fiber
lanes: Lanes,
) :Fiber | null {
// If newChild is a Fragment node with no key set (top-level node)
// Assigns its child to newChild
const isUnkeyedTopLevelFragment =
typeof newChild === 'object'&& newChild ! = =null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
const isObject = typeof newChild === 'object'&& newChild ! = =null;
if (isObject) {
// newChild is an object
switch (newChild.$$typeof) {
// Single node type
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
// The following case is handled similarly to the single-node type. }if (isArray(newChild)) {
// Array type
returnreconcileChildrenArray( returnFiber, currentFirstChild, newChild, lanes, ); }...// Invalid object type
throwOnInvalidObjectType(returnFiber, newChild);
}
// Text node
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
' '+ newChild, lanes, ), ); }...// Update deletes all nodes
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
Copy the code
reconcileSingleElement
Handle New Fiber as a single node
function reconcileSingleElement(
returnFiber: Fiber, / / workInProgress namely
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
) :Fiber {
const key = element.key;
let child = currentFirstChild; // Old fiber
// If child is not null, loop through
while(child ! = =null) {
if (child.key === key) {
// The key value is the same
const elementType = element.type;
if (elementType === REACT_FRAGMENT_TYPE) {
if (child.tag === Fragment) {
// Same type, can reuse Old fiber, delete sibling nodes
deleteRemainingChildren(returnFiber, child.sibling);
constexisting = useFiber(child, element.props.children); existing.return = returnFiber; .returnexisting; }}else {
if (
child.elementType === elementType ||
(__DEV__
? isCompatibleFamilyForHotReloading(child, element)
: false) ||
(enableLazyElements &&
typeof elementType === 'object'&& elementType ! = =null &&
elementType.$$typeof === REACT_LAZY_TYPE &&
resolveLazy(elementType) === child.type)
) {
// Same type, can reuse Old fiber, delete sibling nodes
deleteRemainingChildren(returnFiber, child.sibling);
constexisting = useFiber(child, element.props); existing.ref = coerceRef(returnFiber, child, element); existing.return = returnFiber; .returnexisting; }}// Delete the current child
deleteRemainingChildren(returnFiber, child);
break;
} else {
Delete the current child because the key value is inconsistent
deleteChild(returnFiber, child);
}
// The current child cannot be reused
// Use child's sibling node to continue the comparison (optimization point)
child = child.sibling;
}
// If there are no reusable ones, go to create them here
if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
lanes,
element.key,
);
created.return = returnFiber;
return created;
} else {
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
returncreated; }}Copy the code
reconcileSingleTextNode
Next deal with the New fiber of the text node type
function reconcileSingleTextNode(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
textContent: string,
lanes: Lanes
) :Fiber {
if(currentFirstChild ! = =null && currentFirstChild.tag === HostText) {
// Delete sibling nodes of the same type
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
const existing = useFiber(currentFirstChild, textContent);
existing.return = returnFiber;
return existing;
}
// The type is inconsistent and cannot be reused. Delete nodes and create new nodes
deleteRemainingChildren(returnFiber, currentFirstChild);
const created = createFiberFromText(textContent, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
}
Copy the code
reconcileChildrenArray
Finally, there is the New Fiber that deals with array types, and this is the part that most articles discuss the most
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
lanes: Lanes,
) :Fiber | null {...let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
// First traversal
for(; oldFiber ! = =null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
// The old node is to the right of the new node
// Step 1: In the next loop, the old node remains unchanged and the new node is taken to the right
nextOldFiber = oldFiber;
oldFiber = null;
} else {
// Step 2: Use the sibling of the old node as the old node for the next iteration (at the end of the loop, assign nextOldFiber to oldFiber)
nextOldFiber = oldFiber.sibling;
}
// Step 3: Check whether nodes can be reused by key
// If reusable, return the fiber created by the old node
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes,
);
if (newFiber === null) {
// Step 4: Unreusable nodes, out of the loop
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// Step 5: Delete the old nodedeleteChild(returnFiber, oldFiber); }}// Step 6: Update operationlastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); .// Step 7: Assign the latter node to oldFiber
oldFiber = nextOldFiber;
}
// The first traversal is complete
if (newIdx === newChildren.length) {
// Step 8: Delete the remaining old nodes after all new nodes are traversed
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
// The old node is traversed
// Step 9: If there are any new nodes, enter the following loop to create them one by one
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// Step 10: Use Map to save the old node information
// If the old node has a key value, use the key value as the Map key value
// If no key value exists, use the subscript as the Map key value
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// A node that cannot be reused during the first loop will jump out and go through the second loop
// The second pass
for (; newIdx < newChildren.length; newIdx++) {
// Step 11: Check whether existingChildren has the same old node by the key value or index of the new node
// If not, a new Fiber will be created for the new node
// More on this in the next section
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes,
);
if(newFiber ! = =null) {
if (shouldTrackSideEffects) {
if(newFiber.alternate ! = =null) {
// Step 12: If newFiber is an old node for reuse, delete the corresponding node in existingChildren
existingChildren.delete(
newFiber.key === null? newIdx : newFiber.key, ); }}// Step 13: Update operationlastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); . }}// Step 14: Clear existingChildren after completing the second walk
if (shouldTrackSideEffects) {
existingChildren.forEach(child= > deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
Copy the code
Tool function
With the process almost complete, let’s take a look at some important utility functions
Handles the insertion of text nodes in the reconcileChildFibers method
ShouldTrackSideEffects specifies whether to add an effectTag to the Fiber object
// shouldTrackSideEffects is true for update
// The alternate property is null, indicating that the fiber is not yet inserted into the Dom
function placeSingleChild(newFiber: Fiber) :Fiber {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.effectTag = Placement;
}
return newFiber;
}
Copy the code
It is used in the second pass of the reconcileChildrenArray method
// Iterate over the old nodes at the current level to get a Map with key or index as key and node as value
function mapRemainingChildren(returnFiber: Fiber, currentFirstChild: Fiber) :Map<string | number.Fiber> {
const existingChildren: Map<string | number, Fiber> = new Map(a);let existingChild = currentFirstChild;
while(existingChild ! = =null) {
if(existingChild.key ! = =null) {
existingChildren.set(existingChild.key, existingChild);
} else {
existingChildren.set(existingChild.index, existingChild);
}
existingChild = existingChild.sibling;
}
return existingChildren;
}
Copy the code
It is used in the second pass of the reconcileChildrenArray method
// Check whether existingChildren has the same old node by the key value or index of the new node
function updateFromMap(
existingChildren: Map<string | number, Fiber>,
returnFiber: Fiber,
newIdx: number,
newChild: any,
lanes: Lanes
) :Fiber | null {
if (typeof newChild === "string" || typeof newChild === "number") {
const matchedFiber = existingChildren.get(newIdx) || null;
return updateTextNode(returnFiber, matchedFiber, "" + newChild, lanes);
}
if (typeof newChild === "object"&& newChild ! = =null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const matchedFiber =
existingChildren.get(newChild.key === null ? newIdx : newChild.key) ||
null;
return updateElement(returnFiber, matchedFiber, newChild, lanes);
}
case REACT_PORTAL_TYPE: {
...
}
if (isArray(newChild) || getIteratorFn(newChild)) {
const matchedFiber = existingChildren.get(newIdx) || null;
return updateFragment(returnFiber, matchedFiber, newChild, lanes, null); }... }...return null;
}
Copy the code
It is used in both cycles in the reconcileChildrenArray method
function placeChild(newFiber: Fiber, lastPlacedIndex: number, newIndex: number,) :number {
newFiber.index = newIndex;
if(! shouldTrackSideEffects) {// No update required
return lastPlacedIndex;
}
const current = newFiber.alternate;
if(current ! = =null) {
const oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// If the old reusable node is to the left of the new node, move the old reusable node to the right
newFiber.flags |= Placement;
return lastPlacedIndex;
} else {
/ / don't touch it
returnoldIndex; }}else {
// Insert a new node
newFiber.flags |= Placement;
returnlastPlacedIndex; }}Copy the code
summary
-
If the New fiber type is text, check whether the New fiber type and the Old fiber type are text (the text content need not be the same).
- If yes, delete the sibling node of Old Fiber and reuse Old fiber
- If not, delete the Old fiber and create a new text node
-
If the New fiber is a single node, check whether the key values are the same
- If yes, check whether the node types are the same
- If yes, delete the sibling nodes of the Old fiber and reuse the Old fiber
- If no, delete the current Old fiber and use the sibling node of the current Old fiber to determine the key value and type again
- If no, delete the current Old fiber and use the sibling node of the current Old Fiber to determine the key value and type again
- If yes, check whether the node types are the same
-
If New Fiber is array type (multiple nodes exist)
- The first time through, compareThe same locationTo check whether the key values of the old and new nodes are the same
- If yes, the current node can be reused
- Inconsistent, then out of the loop
- The first traversal is complete
- If the new node is traversed and the old node is still available, delete the old node and return the result to end the reconciliation process
- If the old node is traversed and the new node is left, the new node is traversed to create a new Fiber and inserted, and the result is returned to end the reconciliation process
- If both the Old and new nodes are left, i.e. the first iteration is out of the loop, then the second iteration is started by traversing the Old fiber to get a Map with the Key value or index as the Key value and the Old node as the value
- For the second traversal, check whether there are nodes with the same key value in Old Fiber according to the Map obtained in the previous step
- If yes, remove the old node, reuse the old node, insert the node, and then delete the corresponding old node in the Map
- If no, create (in
updateFromMap
In the implementation)
- The first time through, compareThe same locationTo check whether the key values of the old and new nodes are the same
lastPlacedIndex
Finally, let’s see what the last place index through diff actually does.
LastPlacedIndex records the position of the last node. During the update operation, compare the index of the node to be updated with that of lastPlacedIndex. Move the node to the right only when the index is smaller than lastPlacedIndex.
Vue’s diff algorithm allows nodes to be moved left and right, while React’s policy allows nodes to be moved right only
The article is also posted on my official account, welcome to follow MelonField
reference
- Github.com/facebook/re…
- Zh-hans.reactjs.org/docs/react-…
- Blog.csdn.net/susuzhe123/…
- www.jianshu.com/p/6d7411c85…