Viii. Component update
When the initial render is completed, the component’s reactive data is updated, triggering the getter for the render Watcher again. The process of updating the component is called vm._update(vm._render(), hydrating). PrevVnode = true; prevVnode = true; prevVnode = true; prevVnode = true; Then vm.$el = vm. __Patch__ (prevVnode, vnode) is executed. The first parameter is passed to the previous vNode and the new parameter is passed to the generated vNode. Vm. __patch__ is actually the patch function in patch.js. In patch, else logic will be executed because oldVnode is defined. Oldvnode.nodetype = oldvNode.nodeType = sameVnode(oldVnode, vNode); The sameVnode function attempts to get the keys of the two vNodes that are passed in. The keys are very common when writing v-for. If they have the same key, the sameVnode function continues to determine if they have the same tag. Return true if a is an asynchronous placeholder and a.asyncFactory === b.asyncFactory and b is executed correctly. Otherwise, false is returned, which means that SamevNode determines whether the two old and new nodes are the same. PatchVnode (oldVnode, vNode, insertedVnodeQueue, NULL, NULL, removeOnly) if the above two conditions are met, then patchVnode(oldVnode, vNode, insertedVnodeQueue, NULL, NULL, removeOnly) will be executed. If the new node is different from the old one, he will deal with it in three steps
- Step 1 Create a new node
Nodeops.parentnode (oldElm); create a new DOM node by calling createElm. After this step, a new node is created and inserted, meaning that both the new node and the old node are present on the page.
- The second step is to recursively update the parent placeholder node
Vnode. parent is defined at the end of _render, equal to vm.$options._parentVnode, which is the placeholder of the parent. It then executes isPatchable(vNode). The isPatchable function loops to see if there is a vnode.componentInstance. If there is, it is a component vnode. Then vnode = vnode.ponentinstance. _vnode will continue to find its render vNode until it finds its actual render node. If there is a parent placeholder, execute the destroy hook. Elm = vnode.elm; elm = vnode. Elm = vnode. Elm = vnode. Elm = vnode. Ancestor = ancestor. Parent; ancestor = ancestor. Parent; ancestor = ancestor. Parent; ancestor = ancestor.
- Step 3 Delete the old node through
removeVnodes([oldVnode], 0, 0)
Delete the old node
return function patch (oldVnode, vnode, hydrating, removeOnly) {...let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
...
} else {
const isRealElement = isDef(oldVnode.nodeType)
if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly)
} else{...// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
/ / # 6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0.0)}else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
Copy the code
If sameVnode(oldVnode, vnode) is true, i.e. they have the same key, same data, etc., the patchVnode function is executed.
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
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)
}
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
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)
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)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}
Copy the code
- Just text substitution
Let’s imagine a scenario like this
<template>
<div id='app'>
<div v-if="flag" @click="flag = false">123</div>
<div v-else @click="flag = true">444</div>
</div>
</template>
Copy the code
When I click on div and trigger flag=false, the patchVnode function will first define oldCh and ch, which are children of the old vnode and the new vnode respectively. When entering the patchVnode function for the first time, it will initially compare from
- Array push operation
Let’s imagine a scenario like this
<template>
<div id="app">
<ul>
<li v-for="item in arr" :key="item.id">{{ item.text }}</li>
</ul>
<button @click="arr.push({ id: 3, text: 3 })">add</button>
</div>
</template>
<script>
export default {
data() {
return {
arr: Array.from({ length: 3 }).map((item, index) = > ({
id: index,
text: index
}))
}
}
}
</script>
Copy the code
In this case, ul child elements in the page have three li, li’s key is 0,1,2, and the text content in div is also 0,1,2. When you press button and push a {id: 3, text: 3} into UL’s updateChildren function, oldCh is the vNode of 3 Li and newCh is the vnode of 4 Li. (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) = (0<=2)&&(0<=3) = true Else if (sameVnode(oldStartVnode, newStartVnode)) is true If the patchVnode of oldStartVnode and newStartVnode is executed, li with key 0 of the old vnode and Li with key 0 of the new vnode have the same text node, nothing will be executed when patchVnode with key 0 is executed. To return ul’s updateChildren function, oldStartVnode = oldCh[++oldStartIdx]; NewStartVnode = newCh[++newStartIdx] oldStartIdx changes from 0 to 1, oldStartVnode points to the second LI, newStarttidx and newStartVnode are the same. While (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) 1<=3&&1<=4, that is, until the third li comparison is performed, nothing is done. After comparing the third li, oldStartIdx is 3,oldStartVnode is undefined, newStartIdx is also 3, newStartVnode is the newly created vnode with li key 3. If (oldStartIdx > oldEndIdx) if (oldStartIdx > oldEndIdx) if (oldStartIdx > oldEndIdx) if (oldStartIdx > oldEndIdx) The remaining nodes are then inserted into addVnodes
- Pop operations on arrays
After executing ul’s updateChildren while, newStartIdx is 3 and newEndIdx is 2, the removeVnodes operation is performed to remove excess VNodes.
- Reverse operations on arrays
Else if(sameVnode(oldStartVnode, newEndVnode)) InsertBefore (parentElm, oldStartvNode. elm, nodeops.nextsibling (oldEndvNode.elm))) 2,0. Then oldStartIdx becomes 1, oldStartVnode points to it, newEndIdx becomes 1, newEndVnode points to it. Else if (sameVnode(oldStartVnode, newEndVnode))), insert li with key 1 before Li with key 0
As you can see, the diff of the same node is compared recursively, rather than being completely deleted and recreated, which is another layer of optimization done by VNode