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 throughremoveVnodes([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

. If oldCh and ch are both defined, the updateChildren function will be executed. The updateChildren function defines oldStartVnode as the start node of the children of the old vNode (the first item in the children array of the old vNode), oldEndVnode as the end node of the children of the old vnode, NewStartVnode Children start node of the new VNode, newEndVnode children end node of the new vNode, Then oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx, because our #app.div is now both new and old div, so all four values are 0, If (isUndef(oldStartVnode)) is undefined, else if (isUndef(oldStartVnode)) is undefined. Else if (sameVnode(oldStartVnode, newStartVnode))), PatchVnode (oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) is executed. , patchVnode is executed again, his oldVnode and vnode are vnodes of the two old and new divs. Similarly, they will first define their own children, namely vnodes of the two text nodes 123 and 456, so div also has no text at this time. At the same time, if both text vnodes have children, then updateChildren will be executed again and logic judgment will be executed again until it reaches the previous Samevnode. Then patchVnode will be executed again. This time, the children of the two text vnodes will be undefined. Else if (oldvNode.text! == vnode.text) the text of one is 123 and the text of the other is 444. Then, execute nodeops.settextContent (elm, vnode.text) to replace the text. At this point, the recursion is complete and we go back to the last call to updateChildren, which is the updateChildren of two divs under two #app.div, OldStartVnode = oldCh[++oldStartIdx] oldStartVnode = undefined,newStartVnode = newCh[++newStartIdx], If oldStartIdx > oldEndIdx and newStartIdx > newEndIdx are not met, the execution ends. The same applies to updateChildren of #app.div.
  • 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