_update
這個方法是幹什麼的呢,前面我們生成了vnode,下一步就是把vnode映射到真實的DOM節點上,這裏包含了初次渲染和後面的更新渲染,拿首次渲染爲例,整理參數之後會去調用vm.__patch__方法,接着會去判斷瀏覽器環境或node.js上,前者轉到patch函數的調用,後者返回一個空函數,因爲在服務端是不存在DOM的。
讓我們把目光轉到patch函數上,他是createPatchFunction函數返回的結果:
export const patch: Function = createPatchFunction({ nodeOps, modules })
看一下函數簽名,接受一個對象,前者是一些常見的DOM操作,後者則是對常見attributes的一些鉤子函數的處理。
在createPatchFunction中定義了很多輔助函數,最後返回的還是一個patch,回顧整個過程,其實是使用了柯里化的技巧,整個createPatchFunction過程使用到的一些函數都是與平臺強相關的,所以相當於把與平臺強相關的內容體取出來集成到了createPatchFunction之中,在patch中可以忽視平臺的差異化參數。
patch
略過一系列定義的函數,在800多行我們終於能看到patch的全貌:
return function patch(oldVnode, vnode, hydrating, removeOnly) {
...
}
由函數調用時:
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
第一個參數是真實的DOM,第二個則是之前的虛擬DOM,後面同樣是涉及服務端渲染,暫時捨棄,默認爲false。這是初始化的時候的調用,如果是更新過程,則調用如下:
vm.$el = vm.__patch__(prevVnode, vnode);
接着往下走,對於第一次渲染,會走到:
oldVnode = emptyNodeAt(oldVnode);
這裏其實就是把真實的DOM轉化爲vnode,接着便是最重要的把vnode掛載到document內部,這裏利用了createElm函數。
在createElm內部,我們又會遇到一個常見的錯誤:
warn(
"Unknown custom element: <" +
tag +
"> - did you " +
"register the component correctly? For recursive components, " +
'make sure to provide the "name" option.',
vnode.context
);
這裏即組件沒有註冊時會出現的錯誤。
接着我們會轉到一個很重要的邏輯,即創建真實的DOM節點:
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode);
setScope(vnode);
這裏根據有無namespace分成兩種,我們先看nodeOps.createElement,這其實就是我們用的dom的操作的簡單封裝,接着會走一個createChildren邏輯,即如果有子節點依次創建即可:
createChildren(vnode, children, insertedVnodeQueue);
接着會使用:
insert(parentElm, vnode.elm, refElm);
方法去在父節點添加子節點,同樣對於insert方法,也只是對原生DOM的簡單封裝,只不過會做一些平臺校驗之類的額外工作。
當然對於順序來說,還是先創建子節點,然後再去遞歸創建父節點,最終掛載到響應的位置。
destroy old
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
掛載成功還有最後一步,舉例來說,這裏我們會創建一個新的div id=app,而原來舊的div id=app的空姐的仍然存在,所以最後一步需要把舊的節點銷燬,這樣才走完了全部的數據驅動流程。
總結
數據驅動答題流程就這樣結束了,當然這裏至少簡單分析了初次掛載,後面會更加深入的介紹,總體來說收穫還是很大的,繼續加油