文章首發於 http://shuaizhang.tophtml
以前有簡單看過 Vue patch 部分的源碼,瞭解了是基於 Snabbdom 庫實現的。最近想詳細瞭解下 Vue 處理 vnode patch 的整個過程,想知道它在 Snabbdom 之上作了哪些事情?因此帶着這個問題,寫了這篇文章來記錄。vue
A virtual DOM library with focus on simplicity, modularity, powerful features and performance.
(一個虛擬 DOM 庫,專一於簡單性,模塊化,強大的功能和性能。)
Snabbdom 核心代碼大約只有 200 行。它提供了模塊化架構,具備豐富的功能,可經過自定義模塊進行擴展。在瞭解核心 patch 前,須要先了解 snabbdom 的模塊化架構思想。node
在節點的生命週期裏作一些任務,來擴展 Snabbdom ,就能夠稱之爲模塊。
Snabbdom 在 patch 的過程當中會注入不少鉤子(hooks)。模塊實現就是基於這些鉤子,鉤子能夠理解爲 vnode 節點的生命週期。git
好比 eventlisteners 模塊:github
Hooks
是一種掛載 vnode 生命週期的方式。軟件開發領域有相似這樣的設計思想,好比版本管理工具 git
。Snabbdom 中的模塊就是基於此來實現擴展的。固然,也能夠傳遞 hook 配置,實如今 vnode 生命週期裏作一些事(這種只針對單個節點,而模塊針對全部節點),例如:web
h('div.row', { key: movie.rank, hook: { insert: vnode => { movie.elmHeight = vnode.elm.offsetHeight } } })
Vue 中的指令就是基於此實現的。具體的生命週期能夠參考官方文檔:https://github.com/snabbdom/snabbdom#hooks數組
Snabbdom 的核心部分,patch 函數由 snabbdom.init
建立,初始化時提供綁定 hook 的模塊。
若是 oldVnode
是具備父節點的 DOM 元素,則 newVnode
將變爲 DOM 節點,而且傳遞的元素將被建立的 DOM 節點替換。若是傳遞的是個 vnode
實例,則會比對此節點和遞歸對比它的子節點,並作 dom 更新。這一塊網上的源碼解析文章也比較多,這裏就很少介紹了。性能優化
h
函數用來建立 vnode 實例,類比 Vue render 函數中的參數 h
,Vue 中擴展了入參。好比第一個參數 tag
,Vue 能夠是對象或函數;第二個參數 data
,Vue 增長了一些特有的功能好比:scopedSlots
, slot
, directive
, ref
... 等。架構
init
, prepatch
, insert
, destroy
。當父組件 patch 執行的過程當中,遇到子組件的話,會調用 hook: init 方法,此方法中才會初始化組件實例。後續 insert hook 觸發時,相應的會調用組件 mounted
生命週期鉤子。這一塊源碼能夠查看 https://github.com/vuejs/vue/blob/dev/src/core/vdom/create-component.js#L36-L97 setScope
也是在 patch 執行過程當中調用的,它會往 dom 節點上增長 ${scopedId}
屬性,用來實現 scoped
樣式功能。ref
到上下文實例上。pre
和 post
2 個鉤子,這 2 個鉤子分別用來在 patch 執行先後調用的init
鉤子只對組件節點生效,而 Snabbdom 中全部節點均可定義 init
hooksactive
鉤子,用來處理 <keep-alive>
嵌套 <transition>
組件產生的邊界問題 Vue 會對靜態節點作性能優化。dom
staticRenderFns
屬性裏。除了純 html 語法產生的靜態節點外,v-once
, v-pre
也會產生靜態節點。_staticTrees
數組中。組件更新從新觸發 render 時,不會從新建立 vnode 節點,直接使用以前已有的靜態節點。進而不會觸發 patchVnode
操做。對於 Vue 服務端渲染輸出的 html,客戶端初始化掛載節點時,會把已經渲染好的 dom 和 vnode 一一綁定,以達到同構的效果,hydrate
函數就作了這個任務。
我整理了一份比較詳細的 patch
流程圖。
一些細節沒有寫入,好比:updateChildren
diff 過程、異步組件、keep-alive、hydrate...
Vue 2 中的指令就是基於 hooks 實現的,從 directive 的生命週期來看:
vnode 建立、更新、銷燬時都會更新所在組件上的 $refs
屬性。
patch 過程當中處理了子組件爲空時,父組件指向的 ref 爲 undefined 的邊界狀況
對於動態綁定 style,Vue 還會智能的往不支持的屬性前加廠商前綴。
const normalize = cached(function(prop) { emptyStyle = emptyStyle || document.createElement('div').style prop = camelize(prop) if (prop !== 'filter' && prop in emptyStyle) { return prop } const capName = prop.charAt(0).toUpperCase() + prop.slice(1) for (let i = 0; i < vendorNames.length; i++) { const name = vendorNames[i] + capName if (name in emptyStyle) { return name } } })
Vue 還支持 value 數組寫法。好比 {display: ["-webkit-box", "-ms-flexbox", "flex"]}
Snabbdom 中對 style 增長了 hooks: remove,這個是爲了實現節點被移除時的過渡動效,而 Vue 對過渡動效的處理封裝在了 <transition>
組件中。
其實 Vue 中還有不少功能都依賴 vnode 節點 patch 的過程,transition
的功能也比較多,這裏暫時不深刻了。
因爲本人理解有限,文中若有任何問題歡迎留言指正。