Vue如何用虛擬dom進行渲染view的

前提

vue版本:v2.5.17-beta.0

觸發render

vue在數據更新後會自動觸發view的render工作,其依賴於數據驅動;在數據驅動的工作下,每一個vue的data屬性都被監聽,並且在set觸發時,派發事件,通知收集到的依賴,從而觸發對應的操作,render工作就是其中的一個依賴,並且被每一個data屬性所收集,因此每一個data屬性改變後,都會觸發render。

vue更新監聽

看一段代碼

// 來自mountComponent函數
updateComponent = function () {
  vm._update(vm._render(), hydrating);
};

new Watcher(vm, updateComponent, noop, {
  before: function before () {
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate');
    }
  }
}, true /* isRenderWatcher */);

 

updateComponent是更新組件的函數,內部調用vm._update,並且傳參vm._render();

  • vm._render()返回了什麼?看源碼則得知返回了虛擬dom(VNode)
  • vm._update函數又做了什麼事情?顧名思義,更新傳入的vnode
  • 什麼時候觸發updateComponent函數?在任何vue的data屬性更改值都會觸發

更新view

閱讀_update函數得知,最終調用了vm.__patch__方法,最終找到爲createPatchFunction方法的返回值

var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });

Vue.prototype.__patch__ = inBrowser ? patch : noop;

 

接下來重點看createPatchFunction的返回函數patch.

  1. 如果新的vnode不存在,則註銷舊的vnode
if (isUndef(vnode)) {
  if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
  return
}

 

  1. 如果舊的vnode不存在,則創建新的vnode
if (isUndef(oldVnode)) {
  // empty mount (likely as component), create new root element
  isInitialPatch = true;
  createElm(vnode, insertedVnodeQueue);
}

 

  1. 如果以上不成立,則新的vnode和oldVnode都存在.如果oldVnode不是真實的dom,則爲虛擬dom節點,並且新老vnode相似,則進行diff算法
var isRealElement = isDef(oldVnode.nodeType);
if (!isRealElement && sameVnode(oldVnode, vnode)) {
    // patch existing root node
    patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
}

 

  1. 如果新老vnode不同,則拿到oldVnode的父節點,輔助創建vnode的新節點
var oldElm = oldVnode.elm;
var 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)
);

 

以上的步驟發現,更新view時,重點進入到了patchVnode函數,因此下面進入patchVnode的函數閱讀

  1. 如果新老node相等,則不做處理
if (oldVnode === vnode) {
  return
}

 

  1. 如果vnode不是文本節點則有幾種情況
if (isDef(oldCh) && isDef(ch)) {
  // 如果oldVnode和vnode的children都有值(組件層),並且不想等,則執行更新children流程
  if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
} else if (isDef(ch)) {
  // 如果vnode的children有值,如果當前dom有文本則清空,
  // 並將oldVnode的dom作爲父節點,創建新vnode的children節點
  if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); }
  addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
  // 如果oldVnode存在children,但是新的沒有,則刪除oldVnode的children的vnode
  removeVnodes(elm, oldCh, 0, oldCh.length - 1);
} else if (isDef(oldVnode.text)) {
  // 如果oldVnode有文本信息,則將dom的文本清空
  nodeOps.setTextContent(elm, '');
}

 

  1. 如果vnode是文本節點, 則當新老節點文本不同,將dom的文本更新成新vnode的文本
else if (oldVnode.text !== vnode.text) {
  nodeOps.setTextContent(elm, vnode.text);
}

 

patchVnode函數處理的情況梳理一下則爲:

  1. 如果新老vnode相同,不作處理
  2. 如果新vnode是文本節點,並且新老文本不同,將dom更新爲vnode的文本
  3. 如果新老vnode都有children,表示他們是組件層,則調用updateChildren去做組件層更新
  4. 如果新vnode是組件層,oldVnode不是,則將當前dom添加新vnode組件的子元素
  5. 如果oldVnode是組件層,新vnode不是,則操作dom,將oldVnode包含的子元素刪除
  6. 如果新vnode是組件層,oldVnode是文本節點,則將dom的文本清空

在patchVnode部分又浮現了一個新的函數:updateChildren,是在新老vnode都不是文本節點時調用的,這裏就是vue的渲染機制的核心

updateChildren

updateChildren中將新老vnode的children進行的循環處理,每一次循環去判斷是否有相同的vnode,如果不存在或存在但已經不相同則創建新的dom,否則,如果是新老節點相同,回調patchVnode函數去處理2個節點。 這樣進行了遞歸處理,組件層的更新就完成了。

結尾

本文爲看源碼分析vue更新機制部分,省略了特殊場景的源碼分析,比如ssr、靜態組件等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章