patch 主要是對新舊 vnode 進行 diff 比對,最後回傳所創建真正 的 DOM 節點完成畫面更新。
首先回顧一下,當 vm 實例需要更新時,會執行這段程式碼
// src/core/instance/lifecycle.js
vm._update(vm._render(), hydrating)
_update function 裡,vm.__patch__ 會調用 patch function
// src/core/instance/lifecycle.js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    // ...
    if (!prevVnode) {
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false 
    } else {
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    
    // ...
  }
// src/core/vdom/patch.js
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    /*************************************************
      當新 vnode 不存在且舊 vnode 存在時,return 舊 vnode,不執行 patch
    **************************************************/
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }
    let isInitialPatch = false
    const insertedVnodeQueue = []
    /*************************************************
      若舊 vnode 不存在
    **************************************************/
    if (isUndef(oldVnode)) {
      isInitialPatch = true
      /*************************************************
        就創建一個新 vnode
      **************************************************/
      createElm(vnode, insertedVnodeQueue)
    } else {
      /*************************************************
        取得舊 vnode 的 nodeType 来判斷是不是真正的 DOM
      **************************************************/
      const isRealElement = isDef(oldVnode.nodeType)
      
      /*************************************************
        如果不是真正的 DOM 且 `oldVnode` 和 `vnode` 是相同節點,就會進入 if 内部,執行`patchVnode`
      **************************************************/
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
      
       /*************************************************
         `patchVnode` 就是對新舊 vnode 進行 diff 來决定要如何更新
      **************************************************/
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
          // ...
          oldVnode = emptyNodeAt(oldVnode)
        }
        // ...
      }
    }
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    
    /*************************************************
      最後 return 新 vnode 的節點內容
    **************************************************/
    return vnode.elm
  }
判斷 oldVnode 和 vnode 是不是相同的節點透過 sameVnode
// src/core/vdom/patch.js
if (!isRealElement && sameVnode(oldVnode, vnode)) {
    patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} 
sameVnode 判斷條件主要有兩個
都滿足以上條件,會認為是相同 vnode,就會執行 patchVnode
// src/core/vdom/patch.js
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)
      )
    )
  )
}
// src/core/vdom/patch.js
function patchVnode (
    oldVnode,
    vnode,
    insertedVnodeQueue,
    ownerArray,
    index,
    removeOnly
  ) {
    if (oldVnode === vnode) {
      return
    }
    // ...
    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)
    }
  }
cbs 是從下面這段程式碼來的
// src/core/vdom/patch.js
// 
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
cbs.update 執行了下面幾個 call back,都是對當前 vnode 更新的各個階段執行對應的操作
更新完當前 vnode 後,就是對當前 vnode 的 children 做更新
// src/core/vdom/patch.js
    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)
    }
   
這裡分為兩種情況
updateChildren 比較 children 的差異,這裡就是 diff 算法的核心比較到最後都是調用 createElm 創建真正的 DOM,這裡分為兩種情況
createComponent 會 return true,所以不會往下繼續,而會調用 $mount 來掛載元件// src/core/vdom/patch.js
 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm))