vue源码分析(十八)核心函数之patch

我们先打开文件src/core/vdom/patch.js。代码如下:

function patch (oldVnode, vnode, hydrating, removeOnly) {
   // vnode不存在那就销毁旧的vnode
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []
    // 如果旧的vnode不存在,那直接创建新的DOM
    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
        if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (process.env.NODE_ENV !== 'production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                'full client-side render.'
              )
            }
          }
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it
          oldVnode = emptyNodeAt(oldVnode)
        }

        // replacing existing element
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)

        // create new node
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          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)
              }
              // #6513
              // invoke insert hooks that may have been merged by create hooks.
              // e.g. for directives that uses the "inserted" hook.
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
                // start at index 1 to avoid re-invoking component mounted hook
                for (let i = 1; i < insert.fns.length; i++) {
                  insert.fns[i]()
                }
              }
            } else {
              registerRef(ancestor)
            }
            ancestor = ancestor.parent
          }
        }

        // 销毁旧的虚拟node节点
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }
   // 调用‘insert’钩子 
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }

接下来我们就来拆分一下这个函数。

 if (isUndef(vnode)) {
   if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
    return
 }

如果新的vnode不存在的话,那就直接销毁之前的oldVnode,然后return掉了。

if (isUndef(oldVnode)) {
  // empty mount (likely as component), create new root element
  isInitialPatch = true
  createElm(vnode, insertedVnodeQueue)
}

如果没有存在oldVnode,那就直接根据vnode创建真实的DOM。

 const isRealElement = isDef(oldVnode.nodeType)
 if (!isRealElement && sameVnode(oldVnode, vnode)) {
   // patch existing root node
   patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
 }

我们看到这里有一个判断是否为真实DOM节点,大家可去了解DOM的nodeType属性。接下来判断如果是虚拟节点并且是同一个虚拟节点,那就调用patchVnode处理,关于patchVnode的具体代码,请点击查看。

if (isRealElement) {
  if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
    oldVnode.removeAttribute(SSR_ATTR)
    hydrating = true
  }
  if (isTrue(hydrating)) {
    if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
      invokeInsertHook(vnode, insertedVnodeQueue, true)
      return oldVnode
    } else if (process.env.NODE_ENV !== 'production') {
      warn(
       'The client-side rendered virtual DOM tree is not matching ' +
        'server-rendered content. This is likely caused by incorrect ' +
        'HTML markup, for example nesting block-level elements inside ' +
        '<p>, or missing <tbody>. Bailing hydration and performing ' +
        'full client-side render.'
      )
    }
  }
  oldVnode = emptyNodeAt(oldVnode)
}

可以看到如果是真实DOM才会进来。
oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR) 如果是‘div’并且存在‘ssr’属性。

oldVnode = emptyNodeAt(oldVnode)

新建一个空的虚拟节点,执行这行代码的前提是:
1、不存在服务器渲染
2、hydrating 的值为false

// 销毁旧的虚拟node节点
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }

// 调用‘insert’钩子 
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)

最后还会进行 ‘销毁旧的虚拟node节点’、‘调用‘insert’钩子 ’。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容