Diff
The biggest purpose of VNode is to generate virtual DOM nodes corresponding to the real DOM before and after data changes. Then, you can compare the old and new VNodes to find out the differences, and then update the DOM nodes with the differences, and finally achieve the purpose of updating the view with the least operation on the real DOM
The Diff algorithm is at the heart of the virtual DOM
The so-called oldVNode (namely oldVNode) is the virtual DOM node corresponding to the view before the data changes, while the new VNode is the virtual DOM node corresponding to the new view to be rendered after the data changes. Therefore, we need to take the generated new VNode as the benchmark to compare the old oldVNode. If the new VNode has a node but the old oldVNode does not, add it to the old oldVNode. If the new VNode does not have a node but the old oldVNode does, remove it from the old oldVNode. If some nodes exist on both the new VNode and the old oldVNode, the old oldVNode is updated using the new VNode to make the old and new vNodes the same
Summary: Based on the new VNode, the patch process is to modify the old oldVNode to look like the new VNode.
- Create a node: new
VNode
There are old onesoldVNode
No, it’s in the old oneoldVNode
Created in. - Delete a node: new
VNode
There is no oldoldVNode
There is, from the oldoldVNode
Removed. - Update node: new
VNode
And the oldoldVNode
If there is, take a new oneVNode
Prevail, update the oldoldVNode
Static node
<p>I am the words that will not change</p>
Copy the code
This node contains only plain text and no mutable variables. This means that no matter how the data changes, as long as the node is rendered for the first time, it will never change. This is because it contains no variables, so it has nothing to do with any changes in the data. We call this kind of node a static node.
patch
If both VNode and oldVNode are static nodes
As we said, static nodes have nothing to do with any changes to their data, so if they are all static nodes, they will be skipped without processing.
If VNode is a text node
If a VNode is a text node, that means it contains only plain text, then simply check whether the oldVNode is also a text node. If so, compare the two texts. If not, change the text in the oldVNode to be the same as the text in the VNode. If oldVNode is not a text node, then call setTextNode to change it to a text node, whatever it is, with the same text content as VNode.
If VNode is an element node
If a VNode is an element node, the following two cases are subdivided:
-
This node contains child nodes
If the new node contains child nodes, it is necessary to see whether the old node contains child nodes. If the old node also contains child nodes, it is necessary to update the child nodes by recursive comparison. If the old node has no children, so the old node is likely to be empty node or a text node, if the old node is empty the child nodes of the new node in the create a node and then inserted into the old, if the old node is a text node, then the text to empty, then the child nodes in the new node to create a node and then inserted into the old.
-
This node does not contain child nodes
If the node contains no children and it is not a text node, then the node is empty, and it is easy to empty whatever was inside the old node.
/* Update node */
function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
/* If it is exactly the same, return */
if (oldVnode === vnode) {
return
}
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode)
}
// Take the real DOM first
const elm = vnode.elm = oldVnode.elm
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
// // Check whether all nodes are static
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
/ / child nodes
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
// Check if there is a text attribute, if not
if (isUndef(vnode.text)) {
// Whether there are child nodes
if (isDef(oldCh) && isDef(ch)) {
// If there are child nodes, check whether the child nodes are the same. If they are different, update the child nodes
if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) }else if (isDef(ch)) { // If only vNodes have child nodes
if(process.env.NODE_ENV ! = ='production') {
checkDuplicateKeys(ch)
}
/* Determine if oldVNode has text. If so, empty the text and add the child node to the DOM */
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
// Add the child node to the DOM
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
/* oldVNode has child nodes, but VNode has no child nodes
removeVnodes(oldCh, 0, oldCh.length - 1)}else if (isDef(oldVnode.text)) {
/* Both VNode and oldVNode have no child nodes, but oldVNode has text to empty oldNode text */
nodeOps.setTextContent(elm, ' ')}}else if(oldVnode.text ! == vnode.text) {// Is the text attribute of vNode the same as the text attribute of oldVnode?
// If not, VNode's text replaces DOM's text directly
//
nodeOps.setTextContent(elm, vnode.text)
}
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
Copy the code
Update child nodes
This needs to be taken out separately
If both have children and the children are different, the updateChildren function is required
updateChildren
/* When both VNode and oldVNode have children and are not equal, execute this function
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0 /* oldChildren start indexing */
// // newChildren start indexing
let newStartIdx = 0 / * * /
let oldEndIdx = oldCh.length - 1 /* oldChildren end index */
// newChildren ends the index
let newEndIdx = newCh.length - 1
// The first of all unprocessed nodes in oldChildren
let oldStartVnode = oldCh[0]
// Last of all unprocessed nodes in oldChildren
let oldEndVnode = oldCh[oldEndIdx]
// The first of all unprocessed nodes in newChildren
let newStartVnode = newCh[0]
// Last of all unprocessed nodes in newChildren
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
constcanMove = ! removeOnlyif(process.env.NODE_ENV ! = ='production') {
checkDuplicateKeys(newCh)
}
/* oldStartIdx: start index of the old virtual node oldEndIdx: END index of the old virtual node newStartIdx: start index of the new virtual node newEndIdx: start index of the new virtual node */
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
/* If oldStartVnode is undefined, the pointer moves backwards */
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
/* oldEndVnode moves forward if undefined */
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
/* The new VNode is the same as the old VNode, so update the child node and move the pointer back */
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
/* Move the old front to the old back */
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
/* Move the old back to the old front */
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
// If it does not fall under the above four conditions, the patch will be cyclically compared
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
/* This is not a loop, but a map */
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) {
// If oldChildren cannot be found in newChildren
// Add a new node and insert it into the appropriate position
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
// If the child node is found
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
// If the two nodes are the same, the child node is updated
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
/* Move the old node to the front of the old node */
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
// If not, create the node and insert it directly
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
// newStartIdx moves the pointer back
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
/* If oldChildren first completes the loop and inserts all nodes between [newStartIdx, newEndIdx] into the DOM */
refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
/* Delete all nodes between [oldStartIdx, oldEndIdx] */
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}
Copy the code