vue2.x源碼中的占位符
事情的起因是我再次看了這篇掘金文章從一次 vue ssr 渲染客戶端報錯, 來看 ssr 客戶端激活過程,里面寫的在 updateClass() 中, vnode 的 tag 是 div, 而 vnode 的 elm 卻是 comment. 因為 comment 節點是沒有 setAttribute 方法的, 所以就報錯了.
讓我看的不是很懂,特別是 comment 是干什么的我不是很懂,所以我搭了一個項目進行調試,理順我不清楚的地方。
通過本篇文章,您可以學到:
- 怎么搭環境調試 vue2.x 源碼?
- 占位符是什么?有什么用?
- 組件更新階段是怎么更新占位符節點的?
環境搭建
1.我們使用 vue-cli 搭建一個項目:
vue create test-sourcecode
2.在項目里面克隆 vue 源碼庫:
cd test-sourcecode
git clone git@github.com:vuejs/vue.git
3.進入 vue 源碼庫並使用 watch 形式編譯:
cd vue-dev
npm install
npm run dev
4.回到 test-sourcecode 文件夾,加入.eslintignore
文件,禁止 eslint 檢查 vue 源碼:
cd ..
echo 'vue-dev' >> .eslintignore
5.把項目中的import Vue from 'vue'
替換為import Vue from '../vue-dev/dist/vue.js'
6.使用npm run serve
啟動項目即可。在源碼和項目上的改動都會有熱重載效果,可以放心加console.log
看輸出了。
占位符是什么?有什么用?
我們經常在各種源碼分析文章上面看到占位符節點、placeholder節點等,他們是干什么的呢?
這里我就不貼圖或者代碼了,直接說了。vue 的組件在生成 vnode 的過程中,對於原生節點就直接生成 tag 為對應原生 tag 的vnode 節點,而對於組件節點就生成 tag 是vue-component-1-App
的節點,這種節點就是占位符節點,或者叫 placeholder 節點。
這種節點的作用是,只是占據一個位置而已,並不會像原生vnode節點一樣會進行更新,也不會帶上對應組件vnode的數據,它的更新流程是這樣的,在比較新舊占位符節點進行更新的時候:
- 如果新舊占位符節點有不存在的,就直接創建或銷毀占位符節點,同時通過創建或通過vnode hook銷毀對應的組件 vnode。
- 如果新舊占位符節點都存在,但是不是同一個節點,就銷毀舊節點,在就占位符節點的父節點下創建新占位符節點,同時創建對應的組件 vnode。
- 如果新舊占位符節點都存在,並且是同一個節點,則使用vnode hook更新對應的組件 vnode。
注意:上面提到的組件 vnode 指的是,占位符對應的組件生成的組件 vnode,它才是真正控制這個組件更新的 vnode。(另外,函數式組件不會生成占位符節點,因為它沒有生命周期)
那怎么通過占位符節點找到對應的組件 vnode 呢?其實很簡單,在創建占位符節點的時候,會把組件 vnode 掛載到這個占位符節點的componentInstance屬性上,所以這個屬性就指向了組件 vnode。
組件更新節點是怎么更新占位符節點的
具體的更新流程上面已經說了,總的來說,就是使用占位符節點的 vnode hook 來更新組件 vnode ,這里詳細說明一下怎么用 vnode hook 來更新的。
1.判斷這個節點是不是占位符節點,源碼中是通過查看占位符節點有沒有 hook 進行判斷的,如果是的話,就進行 prepatch:
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
2.在進行 prepatch 的時候,會通過 componentInstance 拿到對應的組件 vnode,然后通過 updateChildComponent 方法更新組件 vnode 的props、listeners等。
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
const options = vnode.componentOptions
const child = vnode.componentInstance = oldVnode.componentInstance
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
)
},
3.到這里占位符節點的任務就已經完成了,但是組件只更新了props、listeners等,然后組件自身通過props、listeners這些響應式數據,來實現自身的更新。
其它
在看源碼的時候,還是有幾個小問題的,留待下次解決了:
- 什么是comment 節點?
- 什么是asyncPlaceholder?
- 什么是static vnode?