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

  1. SameVnode (oldStartVnode, newStartVnode) compares both ends
  2. SameVnode (oldEndVnode, newEndVnode) compares two ends
  3. SameVnode (oldStartVnode, newEndVnode) old man + new tail comparison
  4. 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, soa.key === b.keyIf true, proceed to pathVnode to update the node.
  • So sameVnode (oldStartVnode newStartVnode)Two moreAlways prior
  1. A and A are compared to update, because there is no change in the DOM operation

  2. B and B, the update is valid, because there is no change in the DOM operation

  3. F updates C, and the dom is updated due to data changes

  4. C updates D, and the dom is updated due to data changes

  5. The dom is updated due to data changes

  6. 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, soa.key === b.keyIf 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 comparisonComparison.
  1. A and A are compared to update, because there is no change in the DOM operation

  2. B and B, the update is valid, because there is no change in the DOM operation

  3. C and F, it doesn’t work, keep comparing

  4. The two end judgments of F and F are valid, because there is no change in the DOM operation

  5. C and F, it doesn’t work, keep comparing

  6. The two end judgments of E and E are valid. Dom manipulation is not performed because there is no change at all

  7. C and F, it doesn’t work, keep comparing

  8. The two end judgments of D and D are valid. Dom manipulation is not performed because there is no change at all

  9. C and F, it doesn’t work, keep comparing

  10. C and C are valid. Dom manipulation is not done because there is no change at all

  11. 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

  1. 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.
  2. Because keys are not used, paths are assumed to be the same node, and updates are executed sequentially, which may cause some unexpected effects.