# patch

/src/core/vdom/patch

  • patch 操作基本都在createPatchFunction

# createPatchFunction

const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
export function createPatchFunction (backend) {
  let i, j
  const cbs = {}
  
  const { modules, nodeOps } = backend
  
  for (i = 0; i < hooks.length; i++) {
    const evtName = hooks[i]
    cbs[evtName] = []
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][evtName)) {
        cbs[evtName].push(modules[j][evtName])
      }
    }
  }
    
  // ...
    
  return function patch(oldVnode, vnode, hydrating, removeOnly)
}
// 首次 vm.__patch__(vm.$el, vm._vnode, hydrating, false/* removeOnly */)
// vm.__patch__(prevNode, vnode)
function patch(oldVnode, vnode, hydrating, removeOnly) {
  // 如果新 vnode 不存在,而 olaVNode 存在,则销毁 oldVnode
  // vm.$sestory => vm.__patch__(vm._vnode, null)
  if (isUndef(vnode)) {
    // 触发 oldVnode 里 所有chidlren 里 destroy 里的 hook 事件
    if (isDef(oldVnode)) invokeDestroyHook(oldVnode) 
    return
  }
  
  let isInitialPatch = false
  const insertedVnodeQueue = []
  
  if (isUndef(oldVnode)) {
    // 组件的初次渲染
    isInitialPatch = true
    // 会将 vnode 放到 insertedVnodeQueue
    createElm(vnode, insertedVnodeQueue)
  } else {
    const isRealElement = isDef(oldVnode.nodeType)
    
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
      // 不是真实的 DOM 但是新老节点是同一个节点,执行 patch
      patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
    } else {
      // vm.__patch__(vm.$el, vm._vnode, hydrating, false/* removeOnly */) Vue 根组件的首次渲染
      if (isRealElement) {
        // 处理服务端渲染
        if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
          oldVnode.removeAttributte(SSR_AUTH)
          hydrating = true
        }
        if (isTrue(hydrating)) {
          if (hydrate(oldVnode, vnode, isertedVnodeQueue)) {
            invokeInsertHook(vnode, insertedVnodeQueue, true)
            return oldVnode
          } else if (process.env,NODE_ENV== 'production') {
            warn('...')
          }
        }
        // 不是服务端渲染,或者 hydrating 失败,则根据 oldVnode 创建一个 vnode 节点
        // 通过 vm.$el 生成一个空 oldVnode
        oldVnode = new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
      }
      
      // 拿到老节点的元素
      const oldElm = oldVnode.elm
      const parentElm = nodeOps.parentNode(oldElm)
      
      // 基于新 vnode 创建整颗 DOM 并插入到 parentElm 元素
      createElm(
        vnode,
        insertedVnodeQueue,
        oldElm._leaveCb ? null : parentElm,
        nodeOps.nextSibling(oldElm)
      )
      
      // 递归更新父占位符节点元素
      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)
              }
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
                for (let i = 1; i < insert.fns.length; i++) {
                  insert.fns[i]()
                }
              }
          } else {
            registerRef(ancestor)
          }
          ancestor = ancestor.parent
        }
      }
      
      // 没有 parent 代表不在 dom 树中了
      if (isDef(parentElm)) {
        // 运行 oldVnode 的 destroy
        removeVnodes([oldVnode], 0, 0)
      } else if (isDef(oldVnode.tag)) {
        // 运行 oldVnode 的 destroy
        invokeDestroyHook(oldVnode)
      }
    }
  }
  
  // 将 insertedVnodeQueue 触发 insert 回调函数
  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  return vnode.elm
}

# invokeDestroyHook

function invokeDestroyHook(vnode) {
  let i, j
  const data = vnode.data
  if (isDef(data)) {
    if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
    for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
  }
  if (isDef(i = vnode.children)) {
    for (j = 0; j < vnode.children.length; ++j) {
      invokeDestroyHook(vnode.children[j])
    }
  }
}

# sameVnode

function sameVnode (a, b) {
  // key 必须要相同, 标签相同,是否注释节点 是否都有 data 属性
  return (
    a.key === b.key && (
      // 标签相同
      a.tag === b.tag &&
      a.siComment === b,isComment &&
      isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
    ) || (
      // 异步占位符节点
      isTrue(a.isAsyncPlaceholder) &&
      a.asyncFactory === b.asyncFactory &&
      isUndef(b.asyncFactory.error)
    )
  )
}