Next comes the update process

When the responsive properties are modified, the subscribed Watcher is notified of updates, triggering the component to rerender; First, execute the component render function to get the component VNode, and then execute _update

  Vue.prototype._update = function (vnode: VNode, hydrating? : boolean) {
    const vm: Component = this
    const prevEl = vm.$el / / the dom node
    // Obtain the VNode before the update
    const prevVnode = vm._vnode
    // Set activeInstance and return an anonymous function that returns the value of the previous activeInstance
    const restoreActiveInstance = setActiveInstance(vm)
    // Vnode created by the render function of the current vue instance
    vm._vnode = vnode
    if(! prevVnode) { vm.$el = vm.__patch__(vm.$el, vnode, hydrating,false /* removeOnly */)}else {
      / / update
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    // Set activeInstance to the value of the previous VM instance
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
  }
Copy the code

PrevVnode has a value during the update compared to the first render. The value is the VNode before the update. So else logic is used. Else logic also calls vm.__patch__ but passes prevVnode. Note that VNode is set to the latest VNode;

Let’s look at the patch function

return function patch (oldVnode, vnode, hydrating, removeOnly) {
    // The new node does not exist, the old node exists, destroy the old node
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []
    if (isUndef(oldVnode)) {
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)
      if(! isRealElement && sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue,null.null, removeOnly)
      } else {
        if (isRealElement) {
          oldVnode = emptyNodeAt(oldVnode)
        }

        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)
        createElm(
          vnode,
          insertedVnodeQueue,
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )

        // Update the vnode ELM and re-execute the parent component's CBs. create and INSERT hooks.
        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)
            }
            // Update the VNode ELM
            ancestor.elm = vnode.elm
            if (patchable) {
              for (let i = 0; i < cbs.create.length; ++i) {
                cbs.create[i](emptyNode, ancestor)
              }
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
                // Insert hook = mounted //
                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

In the update process, the patch function will appear in three cases, which are:

  • The current component is created for the first time. For example, the parent component passesv-ifControls whether the subcomponent is rendered
  • The new and old nodes are the same
  • The new and old nodes are different

As for the first case, it’s the same as the first rendering process, so I won’t go into much detail here. The following case follows the first logic

<template>
    <div>
        <cmp1 v-if="xxx">xxx</cmp1>
        <cmp2 v-else>yyy</cmp2>
    </div>
</template>
Copy the code

When XXX is changed to false, cmp2 is created and oldVNode of Cmp2 is null. That is, if the component was mounted during the initial render, oldVNode will be present during the update phase, and vice versa

The second and third cases are based on the following logic

// Inside the patch function
// oldVnode is not a real node, and sameVnode returns true
if(! isRealElement && sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue,null.null, removeOnly)
}
Copy the code

SameVnode function

function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}
Copy the code
  • For synchronous components, if twovnodekeyIf they are not equal, they are different; ifkeyThe same continues to judgeisComment,data,inputThe types and so on are the same
  • For asynchronous components, if twovnodekeyIf they are not equal, they are different; ifkeyThe same continues to judgeasyncFactoryWhether or not the same

The new and old nodes are the same

When!!!! IsRealElement && sameVnode(oldVnode, vNode) is set up and patchVnode is executed

function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
  if (oldVnode === vnode) {
    return
  }
  // Assign oldvnode.elm to vnode.elm
  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
  }
  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

  // Update all attributes of the node
  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

The function of patchVnode is to update the ELM properties of VNode. In other words, the following process is actually the process of modifying the DOM tree

His logic is to return if the two nodes are the same. Elm is assigned to vnode.elm and the DOM tree is set for the new vnode. If VNode is a component placeholder VNode, execute VNode’s prepatch hook function to update the subcomponent. Then, get the children of the old and new nodes; Update all functions in CBS. Update and VNode’s update hook function are executed to update all properties of the node. The comparison then begins, and if the new node is a text node and the new and old text are different, the ELM text content is directly replaced. If the new vnodes are not text nodes, then their children are identified and treated in several ways:

  1. New and old nodes have child nodes, and child nodes are not the same, useupdateChildrenFunction to update a child node
  2. If only the new VNode has children, the old VNode is either a text node or has no children. If the old node is a text node clear the text of the node; Then throughaddVnodesInsert all child nodes of the new VNode into the new node in batcheselm
  3. If only the old VNode has child nodes, the new VNode is an empty node. Then all the child nodes of the old VNode pass throughremoveVnodesRemove all
  4. When only the old node is a text node, its node text content is cleared

After that, the Postpatch hook function is executed

updateChildren

In the first example above, the updateChildren function is called when both the old and new nodes have children, and the children are different. Let’s take a look at how the updateChildren function updates a child node. This is actually a recursive procedure. The code is as follows.

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

  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 {
          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

We first define four Pointers and the VNode nodes corresponding to the four Pointers

  • oldStartIdx,oldEndIdx,newStartIdx,newEndIdxThese are the indexes on both sides of the new and old Vnodes
  • oldStartVnode,oldEndVnode,newStartVnode,newEndVnodePoints to the VNode nodes corresponding to these indexes

And then a while loop, oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx; The while loop ends when the start index of the old node is greater than the end index of the old node or the start index of the new node is greater than the end index of the new node.

The logic inside the while loop is as follows

  1. oldStartVnodeIn the case of no,oldStartIdxMove to the center and updateoldStartVnodeThe value of the
  2. oldEndVnodeIn the case of no,oldEndIdxMove to the center and updateoldEndVnodeThe value of the
  3. oldStartVnodenewStartVnodeIt’s the same node, which means the two nodes have the same beginning, callpatchVnodeTo update the child node. After the child node is updated, theoldStartIdxnewStartIdxMove back one bit
  4. oldEndVnodenewEndVnodeIt’s the same node, it’s the same end of the two nodes, it’s the same processpatchVnodeOperation and will beoldEndIdxnewEndVnodeMove forward one bit
  5. oldStartVnodenewEndVnodeIs the same node, that is, the head of the old node and the tail of the new node are the same node, calledpatchVnode, update the child node; After the child node update is complete, theoldStartVnode.elmMove to theoldEndVnode.elmThe back; thenoldStartIdxIf I move back one bit,newEndIdxMove forward one bit.

  1. oldEndVnodenewStartVnodeIs the same node, that is, the tail of the old node and the head of the new node are the same nodepatchVnode, update the child node; After the child node update is complete, theoldEndVnode.elmInserted into theoldStartVnode.elmThe front;oldEndIdxIf I move forward one bit,newStartIdxI’m going to move back one bit.

  1. If all of the above are lost, enter the following logic
 else {
   if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
   idxInOld = isDef(newStartVnode.key)
     ? oldKeyToIdx[newStartVnode.key]
   : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
   if (isUndef(idxInOld)) {
     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 {
       createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
     }
   }
   newStartVnode = newCh[++newStartIdx]
 }
Copy the code

CreateKeyToOldIdx {[name of key]: [index in oldCh]}; If newStartVnode has a key, get the position of that key in oldCh and assign it to idxInOld; Otherwise, oldCh is traversed, looking for the same node as newStartVnode, and if found, the corresponding index is returned and assigned to idxInOld. The logic follows:

  • If idxInOld is undefined, all nodes in newStartVnode and oldCh are different. Call createElm to create the node and insert in front of oldStartVNode.elm. Set newStartIdx one bit later and update the value of newStartVnode

  • If idxInOld has a value, newStartVnode has the same or the same node in oldCh, gets this node, and again uses sameVnode to determine whether this node is the same as newStartVnode

    • If the same callpatchVnodeUpdate the child node. When the child node is updated, this node is removed fromoldCh, and willvnodeToMove.elm(oldCh[key].elm) into theoldStartVnode.elmThe front; letnewStartIdxAnd updatenewStartVnodeThe value of the
    • If not, callcreateElmCreate a node and insert it intooldStartVnode.elmThe front. letnewStartIdxAnd updatenewStartVnodeThe value of the

The following logic is executed when the while loop ends

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
  • ifoldStartIdxIs greater thanoldEndIdxNote The old node is traversed first. And then determinenewCh[newEndIdx + 1]Is there a value? If yes, it indicates that the remaining new nodes (newStartIdxtonewEndIdxBetween nodes) should be inserted intonewCh[newEndIdx + 1].elmThe front; Conversely, insert at the end
  • ifnewStartIdxIs greater thannewEndIdxNote The new node is traversed first. Directly to theoldStartIdxtooldEndIdxDelete all nodes between

Prepatch hook function

In the patchVnode method, if the new VNode is a component placeholder VNode, the prepatch hook function of the VNode is called

  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    // Select options from VNode
    const options = vnode.componentOptions
    // Get the component instance
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
      child,
      options.propsData, // Updated props specifies the latest props of the child component
      options.listeners, // Updated Listeners Custom events
      vnode, // new parent vnode
      options.children // new children)}Copy the code

UpdateChildComponent is called within the Prepatch hook function, passing in the child component instance, the latest Prop data, custom events, the new VNode, and the child node (named slot with the name property specifying the slot content).

The updateChildComponent function looks like this

export function updateChildComponent (
  vm: Component, // Subcomponent instance
  propsData: ?Object,
  listeners: ?Object,
  parentVnode: MountedComponentVNode, / / component vnode
  renderChildren: ?Array<VNode>
) {
  if(process.env.NODE_ENV ! = ='production') {
    // Set this parameter to true so that assignment to props[key] fires the set method without causing the customSetter function to give an error
    isUpdatingChildComponent = true
  }

  const newScopedSlots = parentVnode.data.scopedSlots
  const oldScopedSlots = vm.$scopedSlots
  consthasDynamicScopedSlot = !! ( (newScopedSlots && ! newScopedSlots.$stable) || (oldScopedSlots ! == emptyObject && ! oldScopedSlots.$stable) || (newScopedSlots && vm.$scopedSlots.$key ! == newScopedSlots.$key) )constneedsForceUpdate = !! ( renderChildren ||// has new static slots
    vm.$options._renderChildren ||  // has old static slots
    hasDynamicScopedSlot
  )
  // vm.$options._parentVnode points to the new vNode
  vm.$options._parentVnode = parentVnode
  $vnode indicates the new component vnode
  vm.$vnode = parentVnode // update vm's placeholder node without re-render

  if (vm._vnode) { // update child tree's parent
    // Update the render vNode parent
    vm._vnode.parent = parentVnode
  }
  vm.$options._renderChildren = renderChildren

  vm.$attrs = parentVnode.data.attrs || emptyObject
  vm.$listeners = listeners || emptyObject

  // update props
  / / update the props
  if (propsData && vm.$options.props) {
    toggleObserving(false)
    // Previous propsData
    const props = vm._props
    // A collection of props properties defined by the child component
    const propKeys = vm.$options._propKeys || []
    for (let i = 0; i < propKeys.length; i++) {
      const key = propKeys[i]
      const propOptions: any = vm.$options.props // wtf flow?
      // Change the props value here to trigger the component update
      props[key] = validateProp(key, propOptions, propsData, vm)
    }
    toggleObserving(true)
    vm.$options.propsData = propsData
  }

  // update listeners
  listeners = listeners || emptyObject
  // Get the custom event from the last binding
  const oldListeners = vm.$options._parentListeners
  // Assign this custom event to _parentListeners
  vm.$options._parentListeners = listeners
  updateComponentListeners(vm, listeners, oldListeners)

  // resolve slots + force update if has children
  if (needsForceUpdate) {
    vm.$slots = resolveSlots(renderChildren, parentVnode.context)
    vm.$forceUpdate()
  }

  if(process.env.NODE_ENV ! = ='production') {
    // When the update is complete, set it to false
    isUpdatingChildComponent = false}}Copy the code

When a VNode is updated, a series of properties of the VNode instance are changed, including vm.$VNode updates, slot updates, props updates, and so on. Updates to these properties trigger updates to the child component, which are described in the corresponding article and will not be covered here.

If the child component needs to be updated, then add the child component’s Render Watcher directly to the queue for execution, instead of calling nextTick, because queueWatcher’s flushing method is true, The child component’s Render Watcher is added to the queue at the correct location; Waiting is true, the nextTick method is not called.

After adding to the queue, go back to the patchVnode function and continue updating. When the parent component is updated, the child components are updated in the order in the queue.

The new and old nodes are different

When!!!! When isRealElement && sameVnode(oldVnode, vNode) fails, the following logic is executed

if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
  patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly)
} else {
  if (isRealElement) {
    // Create a vNode based on oldVnode (in this case oldVnode is a real node)
    oldVnode = emptyNodeAt(oldVnode)
  }
  // Get the actual elements of the node
  const oldElm = oldVnode.elm
  // Obtain the parent node of oldVnode
  const parentElm = nodeOps.parentNode(oldElm)

  createElm(
    vnode,
    insertedVnodeQueue,
    oldElm._leaveCb ? null : parentElm,
    nodeOps.nextSibling(oldElm)
  )

  // Update vnode elm and re-execute cbs.create and parent insert hooks.
  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)
      }
      // Update the VNode ELM
      ancestor.elm = vnode.elm
      if (patchable) {
        for (let i = 0; i < cbs.create.length; ++i) {
          cbs.create[i](emptyNode, ancestor)
        }
        const insert = ancestor.data.hook.insert
        if (insert.merged) {
          // Insert hook = mounted //
          for (let i = 1; i < insert.fns.length; i++) {
            insert.fns[i]()
          }
        }
      } else {
        registerRef(ancestor)
      }
      ancestor = ancestor.parent
    }
  }

  if (isDef(parentElm)) {
    removeVnodes([oldVnode], 0.0)}else if (isDef(oldVnode.tag)) {
    invokeDestroyHook(oldVnode)
  }
}
Copy the code

Create the node using createElm as the first time, or in the case of the component placeholder VNode, call the init hook function to create the component instance and perform the mounting process of the component. In the case of a normal VNode, create the node and call createChildren to create the child node, which is inserted into the current node. When this is done, insert the current node into the parent node.

Insert hook function (not including mounted hook function); delete the old node, return the latest DOM tree, and assign the value to vm.$el

The purpose of the insert hook is to prevent the following situation, if there is a custom directive on the component placeholder VNode, and the INSERT callback is bound to the DOM, if not updated, it will always be bound to the old DOM tree, so it needs to be updated

<template>
    <div v-if="xxx">xxx</div>
    <div v-else>yyy</div>
</template>
Copy the code