Skip to content

Patch 渲染过程 ​

patch 函数入口 ​

js
// src/core/vdom/patch.js

function patch(oldVnode, vnode) {
  // ① 如果旧节点不存在(组件初次挂载)
  if (isUndef(oldVnode)) {
    createElm(vnode)  // 直接创建 DOM
  } else {
    const isRealElement = isDef(oldVnode.nodeType)

    if (!isRealElement && sameVnode(oldVnode, vnode)) {
      // ② VNode vs VNode:进入 patchVnode
      patchVnode(oldVnode, vnode)
    } else {
      // ③ 真实 DOM / 不是同一节点:替换
      if (isRealElement) {
        oldVnode = emptyNodeAt(oldVnode) // DOM → VNode
      }
      const oldElm = oldVnode.elm
      const parentElm = nodeOps.parentNode(oldElm)

      createElm(vnode) // 创建新 DOM
      nodeOps.insertBefore(parentElm, vnode.elm, nextSibling(oldElm))
      nodeOps.removeChild(parentElm, oldElm) // 删除旧 DOM
    }
  }
  return vnode.elm
}

patchVnode — 细粒度比较 ​

js
function patchVnode(oldVnode, vnode) {
  // ① 如果是同一个 VNode,跳过
  if (oldVnode === vnode) return

  // ② 拿到真实 DOM
  const elm = (vnode.elm = oldVnode.elm)

  // ③ 异步组件占位
  if (isTrue(vnode.isAsyncPlaceholder)) return

  // ④ 静态节点(且无 key 变化),直接复用
  if (
    isTrue(vnode.isStatic) &&
    isTrue(oldVnode.isStatic) &&
    vnode.key === oldVnode.key
  ) {
    vnode.componentInstance = oldVnode.componentInstance
    return
  }

  // ⑤ 比较子节点
  const oldCh = oldVnode.children
  const ch = vnode.children

  // 当前节点不是文本节点
  if (isUndef(vnode.text)) {
    if (isDef(oldCh) && isDef(ch)) {
      // 都有 children → diff 子节点
      if (oldCh !== ch) updateChildren(elm, oldCh, ch)
    } else if (isDef(ch)) {
      // 只有新 children → 清空旧文本,创建新 children
      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, "")
      addVnodes(elm, null, ch, 0, ch.length - 1)
    } else if (isDef(oldCh)) {
      // 只有旧 children → 清空所有旧子节点
      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)
  }
}

createElm — VNode 变真实 DOM ​

js
function createElm(vnode, parentElm, refElm) {
  const children = vnode.children
  const tag = vnode.tag

  if (isDef(tag)) {
    // ① 元素节点:创建 DOM 元素
    vnode.elm = nodeOps.createElement(tag, vnode)

    // ② 递归创建子节点
    createChildren(vnode, children)

    // ③ 设置属性、事件、样式
    if (isDef(data)) {
      invokeCreateHooks(vnode) // 调用 create hooks
    }

    // ④ 插入父节点
    insert(parentElm, vnode.elm, refElm)

  } else if (isTrue(vnode.isComment)) {
    // 注释节点
    vnode.elm = nodeOps.createComment(vnode.text)
    insert(parentElm, vnode.elm, refElm)

  } else {
    // 文本节点
    vnode.elm = nodeOps.createTextNode(vnode.text)
    insert(parentElm, vnode.elm, refElm)
  }
}

removeVnodes — 批量删除 ​

js
function removeVnodes(vnodes, startIdx, endIdx) {
  for (; startIdx <= endIdx; ++startIdx) {
    const ch = vnodes[startIdx]
    if (isDef(ch)) {
      // 触发 destroy 钩子(清理 Watcher 等)
      if (isDef(ch.tag)) {
        removeAndInvokeDestroyHook(ch)
        invokeDestroyHook(ch)
      } else {
        nodeOps.removeChild(parentElm, ch.elm)
      }
    }
  }
}

基于 Vue 2.7 & Vue 3.4 源码