Example 1.
<! DOCTYPE html><html>
<head>
<title>The functions and working principles of keys in VUE</title>
</head>
<body>
<div id="demo">
<p v-for="item in items" :key="item">{{item}}</p> </div>
<script src=".. /.. /dist/vue.js"></script>
<script>
// Create an instance
const app = new Vue({
el: '#demo'.data: { items: ['A'.'B'.'C'.'D'.'E'] },
mounted () {
setTimeout(() = > {
this.items.splice(2.0.'F')},3000); }});</script>
</body>
</html>
Copy the code
2. The effect to be achieved
Insert the F element before position C
3. Source code analysis
Note that the path process is all about reducing DOM manipulation, and the core of reducing DOM manipulation is finding the same node with no change at all. The method of patchVnode will be triggered whether the node does not use the key or the same node with the same key. The difference is that in a typical Web site, nodes with the same key do not change much, and only a small number of nodes change, thus reducing the actual DOM operations to be performed.Copy the code
Path -> pathVnode -> updateChildren -> sameVnode() method -> pathVnode
// Compare all child nodes
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)) {// Check whether it is the same node. If so, patchVnode updates the node.
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)
}
}
// Compare whether the method is the same node, because
function sameVnode (a, b) {
return (
a.key === b.key && ( // If key is not defined, I therefore pay therefore I am qualified(A.tag === B.tag && Is the same label a.comment === B.isscomment &&// Whether it is also/not an empty node
isDef(a.data) === isDef(b.data) && // Whether data of all corresponding nodes is declared at the same time or not
sameInputType(a, b)// Whether the input type is the same
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
// Update the node
function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
if (oldVnode === vnode) {
return
}
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode)
}
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
}
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
// reset by the hot-reload-api and we need to do a proper re-render.
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)
}
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)
}
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) }else if (isDef(ch)) {
if(process.env.NODE_ENV ! = ='production') {
checkDuplicateKeys(ch)
}
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
removeVnodes(oldCh, 0, oldCh.length - 1)}else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, ' ')}}else if(oldVnode.text ! == vnode.text) { nodeOps.setTextContent(elm, vnode.text) }if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
Copy the code
Judge logical order
- SameVnode (oldStartVnode, newStartVnode) compares both ends
- SameVnode (oldEndVnode, newEndVnode) compares two ends
- SameVnode (oldStartVnode, newEndVnode) old man + new tail comparison
- SameVnode (oldEndVnode, newStartVnode) Compares old tail with new header
3.1 Not Using the Key
- When using updateChildren to compare all child nodes at the same level, use sameVnode to determine if they are the same node.
- Because key is not defined, it defaults to undefined, so
a.key === b.key
If true, proceed to pathVnode to update the node. - So sameVnode (oldStartVnode newStartVnode)
Two more
Always prior
-
A and A are compared to update, because there is no change in the DOM operation
-
B and B, the update is valid, because there is no change in the DOM operation
-
F updates C, and the dom is updated due to data changes
-
C updates D, and the dom is updated due to data changes
-
The dom is updated due to data changes
-
Create E to insert after the latest D
Result: Three DOM updates and one create insert are performedCopy the code
3.2 use the key
- When using updateChildren to compare all child nodes at the same level, use sameVnode to determine if they are the same node.
- Because key is defined, so
a.key === b.key
If they are not the same, they will be executed in sequenceTwo more
.Two tail to compare
.Old man + new tail comparison
.Old tail + new head comparison
Comparison.
-
A and A are compared to update, because there is no change in the DOM operation
-
B and B, the update is valid, because there is no change in the DOM operation
-
C and F, it doesn’t work, keep comparing
-
The two end judgments of F and F are valid, because there is no change in the DOM operation
-
C and F, it doesn’t work, keep comparing
-
The two end judgments of E and E are valid. Dom manipulation is not performed because there is no change at all
-
C and F, it doesn’t work, keep comparing
-
The two end judgments of D and D are valid. Dom manipulation is not performed because there is no change at all
-
C and F, it doesn’t work, keep comparing
-
C and C are valid. Dom manipulation is not done because there is no change at all
-
We’re left with F, so we create the F DOM and insert it in front of the position of C
Result: Only one DOM creation and insertion is performedCopy the code
// first loop patch A ->
A B C D E
A B F C D E
// Patch B ->
B C D E
B F C D E
// Patch E ->
C D E
F C D E
// patch D ->
C D
F C D
// patch C ->
C
F C
// oldCh is finished, the remaining F in newCh is created and inserted in front of C
Copy the code
4. To summarize
- The key is used to accurately determine whether the new and old nodes are the same when creating a path. In this way, different nodes are not frequently updated. Make the update process more accurate, reduce more DOM operations, reduce overall path time, and improve performance.
- Because keys are not used, paths are assumed to be the same node, and updates are executed sequentially, which may cause some unexpected effects.