preface
Finally comes the main part of the DOM diff process: the diff algorithm. The previous process is just an add-on, but the important thing is how the various nodes are compared and then updated. The comparison process of each node is analyzed below.
What was done when vue3.2 was initialized? At the end of the article, it is mentioned that creating a dependency before passing in the effect callback function and reactive data is equivalent to creating a Watcher. When the data changes, the method of parameter two to execute parameter one, the specific details and the scheduler, later again, will eventually enter componentUpdateFn function, we will directly enter the update phase of componentUpdateFn.
patch
Previous treatment
Before starting the patch function, some lifecycle hook functions are executed, with beforeUpdate and VNode’s hooks :beforeUpdate.
The most important thing is that if the child component is updated due to the parent component’s data changes, there will be an additional update to the props and slots and a new VNode, which may result in an update that needs to be executed before patch. Check out my previous articles “Vue3.2vDOM DIff One: Initialization and Updating of slots” and “Vue3.2vDOM DIff One: Initialization and updating of props and attrs”.
After doing this, you can generate a new VNode and pass the old and new vNodes into patch for comparison. Suspense and Teleport diff have been explained in the previous article and will not be mentioned here.
Compare element type nodes
The comparison element goes to processElement, this time to the update process, and executes patchElement. N1 is the old VNode, and n2 is the new VNode
At the beginning of the function, you need to re-validate the new node through the patchFlag of the old node, because you can clone the vnodes generated by complie, and maybe add some new props, such as cloneVNode(VNode, {class: ‘cloneVNode’}), which will select FULL_PROPS.
This is followed by executing the beforeUpdate lifecycle function of the new VNode custom directive. If in dev mode and the HMR is updating, the optimization is abandoned and dynamicChildren is cleared and full diff is used. This affects subsequent diff, but prod mode is generally optimized, and areChildrenSVG is used to determine whether the new VNode is SVG or not.
Here, it is divided into optimization mode and non-optimization mode. The condition for entering optimization mode is that dynamicChildren is not empty and the non-optimization mode is optimized, which is true. However, the two are mutually exclusive, one exists and the other definitely does not exist.
In optimization modediff
After entering this function, it will iterate over the dynamicChildren in the new VNode and extract the nodes in the same order from the dynamicChildren of the old VNode for comparison.
Oldvnode. el is used to ensure that the actual DOM of the element node exists in the case of asynchronous components.
In the presence of oldvNode. el and one of the following three conditions: 2. OldVNode is not the same element as newVNode. 3. OldVNode is a component, which can contain anything. Container is the parent of oldvNode. el. Otherwise, in other cases, there is really no parent container, so passing a block element, avoiding parentNode, is passing fallbackContainer(which is the real DOM of N2),
After confirming the container, oldVNode and newVNode are passed to patch again. Then, according to the node type of newVNode, which branch should be used for diff.
One more thing that needs to be done after the diff process is over. In dev mode, if parentComponent exists and parentComponent is HMR enabled, it needs to recursively find or locate the old EL to reference the update node in case el is NULL is thrown during the update phase. Optimization mode analysis is complete.
The full quantity is carried out in non-optimization modediff
In non-optimization mode, patchChildren was handed over to be processed. Before diff, some things should be obtained: Children of N1 and N2 and shapeFlag of N2. The following process is divided into many situations and analyzed one by one.
fastdiff
Firstly, according to the patchFlag of N2, it is determined whether it can be updated quickly, that is, “targeted update”. After entering, it is divided into two situations: whether it is keyed (whether it is bound with key), and whether the keying can be full keying or mixed keying (some with key and some without key). They were handed to patchKeyedChildren and patchUnKeyedChildren respectively.
Without akey
The contrast of
Because the comparison with the key is a little bit complicated, I’ll put it at the end, but let’s see there’s no key. The comparison without a key is straightforward, because n1 and n2 are not guaranteed to have a children list, and default to an empty array if they do not. Note that we get the length here. We take the smallest of the two list lengths from the old and new children lists as the base, and the next comparison will only be up to this point. Use the diagram to explain.
As shown in the figure, the length of the old children list is 5, and the length of the new children list is 3. The small one is 3, which means that only the first three will be compared in the one-to-one comparison cycle, and the rest will be handed over to the following process.
The rest of the process is divided into two cases. After cyclic comparison, if the length of the new children list is longer than that of the old children list, new nodes will be mounted; otherwise, old nodes that are not needed will be unmounted. The process is complete.
withkey
The contrast of
Back to patchChildren, we will look at the comparison with key and analyze step by step in combination with the figure.
So here’s a couple of things, l2 is the length of the new children list, E1 is the index of the last child in the old children list, e2 is the index of the last child in the new children list. I has a special meaning here, representing the starting index of the comparison. There are five main processes for comparison with key,
If you have the following list of old and new children, you can accurately see that only 2 has moved. Here is how the comparison is made through five processes.
1. Process 1: Compare the starting position
In this stage, the list of old and new children will be traversed, and only when the old and new nodes use one element will they be handed over to the patch function for comparison. After each pair of old and new child nodes, I will be increased by one. If one party traverses to the last one, it will end or two traverses are different elements. In the example, there is no previous same node, so there is no operation
2. Process 2: Compare the position at the end
In this stage, the old and new children lists will be traversed, and the patch function will be compared only when the old and new nodes are the same element as in stage 1. The difference is that the child nodes are compared from the end, and the maximum position index of the old and new will decrease by one at the same time after each pair of child nodes. In the example, from the end 3, 4, 5 are the same elements can be excluded.
The next three processes are to mount the new nodes in the list and unmount the old nodes that are not needed, as well as unordered comparison.
However, only one or none of the three processes will be executed. There are three cases in total: 1. You only need to install new nodes. 2. You only need to uninstall old nodes. 3. If I is greater than E1 and less than or equal to e2, there is a new node, perform flow 3. If I is greater than e2, there is an old node that is not needed, perform flow 4. None of them comply with procedure five
3. Process 3: Mounting a new node (This process may not be performed)
NextPos is used to determine the position of the new node. Generally, at this stage, E2 is the maximum index of the list of new nodes that are not processed. The reason for adding one is that vue adds new nodes through insert, and the implementation principle is insertBefore. So this is going to be the next one after where I’m going to insert the element. See the diagram below for details. (PS: the red box is processed)
In this case, 6 is a new node, because through the process of one and two processing, I became a 5, e2 into 5, 6 indexes, e2 was node if we need to insert it into the list, we need to know who is he after a node, so that as the aiming point, this will add to the list of new children after a find.
ParentAnchor is the last node in the parent container of the current list. ParentAnchor is usually an empty string. (Note: This is a node, not an element node. If not, the process will not be executed
4. Process 4: Uninstall unnecessary old nodes (this process may not be performed)
The operation of unmounting old nodes is relatively simple. For each unmounted I, add one. Unmount the node by finding the parent node and calling removeChildren to unmount the node. If I is greater than e2 but less than or equal to E1. If not, the process will not be executed
5. Flow 5: Out-of-order comparison (this flow may not be executed)
If it comes to flow 5, it means that part of the children list is out of order, and the previous flow cannot deal with it, so out-of-order comparison is needed. The process is divided into three parts.
This first part is to produceindex
And the newchildren
In the listkey
Of the map, it will takei
As the old and new,children
Index of the beginning of the list when foundnewChildren
To be exact, findnewChild
The body of thekey
, will be together withi
Save togetherkeyToNewIndexMap
In the.
The second part is to loop the old node list to match nodes that need to be updated and delete nodes that are not needed. First create an array (newIndexToOldIndexMap) with a number of lengths that need toBePatched (toBePatched) and store the old and new indexes (default to patched all 0).
Start to loop the old children list. Patched over and glee we uninstall nodes whenever patched greater than toBePatched, but patched over 0 is not greater than at the beginning. Move on and start looking for newIndex, first from the key:index map saved in the past. Try to locate the index of a node of the same type without a key in the old children list. Still not have can only undefined.
Finally, if newIndex is undefined, it means that the old node does not have the corresponding new node. Otherwise, the corresponding index position in newIndexToOldIndexMap will be modified. If newIndex is less than the maximum position of the new node (maxNewIndexSoFar), This indicates that the node has been moved, otherwise maxNewIndexSoFar is assigned to newIndex. Patched, we can finally send it to Patch for comparison and patched will add one.
This last part, mainly for moving and adding nodes, will first generate a longest increasing subsequence based on the mapping of the old and new node indexes if necessary. Starting from the end of the loop also allows us to use the last patched node as the aim point, find the longest increasing subsequence in the new node, and move nodes that are not in this range, if mappedoldIndex
If the value is 0, the node is new and needs to be mounted. In this case, it’s going to move by 1.
This process 5 is the most complex, which includes not only mounting and unloading, but also moving nodes to improve the utilization of nodes. The process of patchKeyedChildren is finished here.
Other situations
Go back to patchChildren and see how to compare patchFlag when it does not exist. It should be updated according to the situation of the old and new nodes
If the new node is TEXT_CHILDREN, if the old node is ARRAY_CHILDREN, all the old nodes will be unmounted and the new node will be mounted. The old node will also be TEXT_CHILDREN and the new node will be updated.
If both are ARRAY_CHILDREN, you need to go patchKeyedChldren, but it is also possible to just remove the old node and not the new node, uninstall all the old nodes.
When the old node is TEXT_CHILDREN and the new node is ARRAY_CHILDREN, it becomes an empty string before mounting the new node.
The props section is an example of how to initialize and update props and attrs in vue3.2VDOM DIff.
Compare component type nodes
In the Patch function, the comparison component branch executes processComponent, which eventually executes updateComponent, and component updates inherit the old instance.
Before updating, he should shouldUpdateComponent to see if it needs to be updated. But it is true that there are too many cases, here is not a list, you can check the source code shouldUpdateComponent function.
Suspense(asyncDep exists and asyncResolved does not exist) is not Suspense. In Suspense, assign the new VNode(instance.next) to N2. If the component is already in the update queue, remove it. Avoid repeating updates to the same component, and then you can call the updater on the instance to do the update.
Note that instance.next, if this exists, calls updateComponentPreRender in a call to componentUpdateFn because component data changes cause its children to update, So we need to update the vNodes in the instance as well as props and slots, and we need to update the props as well. If it was a pure data change that didn’t affect the child components, then the Next would be a VNode on the original instance.
The next step is to call the life cycle function and hook function normally, generate the new VNode and the old VNode and hand them to patch for comparison. The next step is to see what is in the component and which process to follow.
Compare text type, comment type, static node type node
- Text type
Updates of text type nodes in processText are compared before the text is updated
- The annotation type
The comment node is updated in processCommentNode, but because there is no support for dynamically updating comments, the previous one is taken directly.
- Static node type
Update of static node is patchStaticNode. Since Vue serializes static node into string, string comparison can be performed directly. If it is the same, only the previous EL and Anchor will be assigned; if it is different, the old one will be cyclically removed first, along with the anchor, and then the new static node will be mounted.
conclusion
This paper analyzed the processing of diFF algorithm in VUE, understood the processing process of DIFF algorithm in VUE, knew how to compare each node, and how to write templates for optimal comparison and reuse of nodes, so as to improve performance. In list comparison, optimization mode only compares nodes in dynmaicChildren. So dynamic nodes, in non-optimized mode, it’s full diff but you can reuse nodes without losing too much performance.
Well, to the end of the article, or I hope you brothers and sisters can guide guidance. Any mistakes or omissions are welcome in the comments section. Thank you.