vue源碼分析(二十)核心函數(shù)之__patch__(更新視圖)

我們打開文件 src/core/vdom/patch.js,找到定義Observer函數(shù)的代碼:

 return function patch (oldVnode, vnode, hydrating, removeOnly) {
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
     // 如果沒有老的node節(jié)點,就用新的vnode節(jié)點去創(chuàng)建。
      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)
        )

        // update parent placeholder node element, recursively
        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
          }
        }

        // destroy old node
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
}

首先如果最新值是 undefined或者 null并且舊值不是undefined或者 null, 那就調(diào)用 invokeDestroyHook函數(shù)(解綁它的指令及事件監(jiān)聽器)。
接下來我們詳細(xì)分析

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

如果沒有老的node節(jié)點,就用新的vnode節(jié)點去創(chuàng)建。createElm 是創(chuàng)建節(jié)點的函數(shù)。
接下來就是存在 oldVnode的情況,

const isRealElement = isDef(oldVnode.nodeType)

首先判斷是否是真實節(jié)點 ,因為每個幾都有 nodeType屬性具體的節(jié)點類型請點擊查看。

 if (isRealElement) {
  // 省略部分代碼
  oldVnode = emptyNodeAt(oldVnode)
}

省略的都是關(guān)于 ssr判斷是代碼,瀏覽器端的話就不用管了。
最后看到,就新建一個空的新節(jié)點賦值給了 oldVnode

// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
createElm(vnode,oldElm._leaveCb ? null : parentElm,nodeOps.nextSibling(oldElm))

定義了 oldElmparentElm 分別保存舊的真實DOM節(jié)點和舊的真實DOM節(jié)點的父節(jié)點。
然后就是調(diào)用 createElm 函數(shù)創(chuàng)建節(jié)點了。可以看到真實的DOM節(jié)點被插入到頁面了。

// destroy old node
 if (isDef(parentElm)) {
    removeVnodes([oldVnode], 0, 0)
 } else if (isDef(oldVnode.tag)) {
    invokeDestroyHook(oldVnode)
 }

移除舊的DOM節(jié)點,解綁它的指令及事件監(jiān)聽器。
最后返回真實的DOM節(jié)點給 $el 屬性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容