vue源码细读 -- 虚拟dom分解 之 patch.js 分解三

patchVnode方法分解

  function patchVnode (
    oldVnode,
    vnode,
    insertedVnodeQueue,
    ownerArray,
    index,
    removeOnly
  ) {
    if (oldVnode === vnode) {
      return
    }

    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // clone reused vnode
      vnode = ownerArray[index] = cloneVNode(vnode)
    }

    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
    }

    // reuse element for static trees.
    // note we only do this if the vnode is cloned -
    // if the new node is not cloned it means the render functions have been
    // reset by the hot-reload-api and we need to do a proper re-render.
    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
    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(elm, 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)
    }
  }


第一个模块

if (oldVnode === vnode) {
  return
}

if (isDef(vnode.elm) && isDef(ownerArray)) {
  // clone reused vnode
  vnode = ownerArray[index] = cloneVNode(vnode)
}

const elm = vnode.elm = oldVnode.elm

注释一下

// 如果两个节点完全一致,则返回。这里比较的是引用路径。
if (oldVnode === vnode) {
  return
}
// 如果真实节点存在,ownerArray存在
if (isDef(vnode.elm) && isDef(ownerArray)) {
  // clone vnode,并重新赋值
  vnode = ownerArray[index] = cloneVNode(vnode)
}
// 将oldVnode的真实节点elm赋值给vnode
const elm = vnode.elm = oldVnode.elm

第二个模块

// vnode/oldVnode均是静态节点,并且key相等,并且 vnode是拷贝出来的或者第一次使用
// 直接将vnode的componentInstance 引用路径修改
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
// 如果存在data属性,并且 存在钩子方法,更新钩子方法
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
  // vnode.data.hook(oldVnode, vnode),对新旧vnode执行hook方法
  i(oldVnode, vnode)
}

const oldCh = oldVnode.children
const ch = vnode.children
// 如果vnode.data存在,并且isPatchable(vnode)
if (isDef(data) && isPatchable(vnode)) {
  // cbs 是一个更新工具对象,通过cbs更新 真实dom的属性
  for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
  // 如果data.hook并且 data.hook.update,对新旧vnode执行hook方法
  if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}

// 是否可以Patch。只有 vnode存在tag时,才能进行patch
function isPatchable (vnode) {
  // vnode 存在componentInstance 指向
  while (vnode.componentInstance) {
    // 将vnode重新赋值为引用对象
    vnode = vnode.componentInstance._vnode
  }
  // 返回是否存在tag标签。
  return isDef(vnode.tag)
}

第四个模块

// 如果vnode.text不存在
if (isUndef(vnode.text)) {
  // 如果新旧vnode都存在子节点
  if (isDef(oldCh) && isDef(ch)) {
    // 如果两个子节点列表不相等,更新子节点
    // 这里是个循环递归,在 updateChildren中还会对不同子节点进行 patchVnode操作
    if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
  }
  // 如果存在新的子节点,旧的vnode不存在子节点
  else if (isDef(ch)) {
    if (process.env.NODE_ENV !== 'production') {
      checkDuplicateKeys(ch)
    }
    // 如果旧的子节点存在text,将elm内容清空
    if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
    // 新增节点
    addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
  }
  // 如果存在旧的子节点,新的子节点不存在
  else if (isDef(oldCh)) {
    // 将旧的子节点清空
    removeVnodes(elm, oldCh, 0, oldCh.length - 1)
  }
  // 如果都不存在子节点,并且旧的节点存在text
  else if (isDef(oldVnode.text)) {
    // 将节点内容清空
    nodeOps.setTextContent(elm, '')
  }
}
  // 如果 新节点的text存在,并且跟旧节点的text不相等
else if (oldVnode.text !== vnode.text) {
  // 将节点的text替换为新节点的
  nodeOps.setTextContent(elm, vnode.text)
}
// 如果存在data
if (isDef(data)) {
  // 如果存在data.hook 和 data.hook.postpatch,对新旧节点执行data.hook.postpatch
  if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}

整理

方法作用:将新旧节点进行合并整理。

方法作用整理

  • 一:如果vnode没有变动,直接返回。
  • 二:如果有变动并且新节点存在真实节点,并且ownerArray存在,将vnode和ownerArray[index]提出来重新赋值
  • 三:如果是静态树,并且vnode的key相等,vnode替换为oldVnode的引用路径
  • 四:如果vnode.data.prepatch存在,对新旧vnode执行hook方法
  • 五:如果vnode.data存在,并且vnode.tag存在,执行cbs.update, 如果data.hook并且 data.hook.update,对新旧vnode执行hook方法

随机浏览