render
在instance的render.js裏面可以找到renderMixin方法,裏面定義了_render方法:
const { render, _parentVnode } = vm.$options
_parentVnode可以先暫時方瞎下,着重看render方法,其實就這一句:
vnode = render.call(vm._renderProxy, vm.$createElement)
前者用來綁定this,生產環境就是vm,開發環境可能是個proxy對象,而後者則是在initRender函數中定義的,這就又回到了第一節的初始化中:
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, "beforeCreate");
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, "created");
這幾個初始化我們之前看過了initState,除去inject和provide,其他應該都是很熟悉的東西,如生命週期,事件機制,再然後就是initRender了。
走到這個函數中,我們可以看到有兩個類似的方法調用:
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
前者是編譯生成render函數創建VNode的方法,典型的有template編譯後的render;後者則是手寫render時執行的方法。
這裏可以稍微看一下createElement的函數簽名:
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
接着如果是後者會走一步:
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
然後繼續傳遞normalizationType的值, 這裏不再展開,後面遇到再講。
扯遠了,回到_renderProxy中。
同樣是在init時做了個判斷:
if (process.env.NODE_ENV !== "production") {
initProxy(vm);
} else {
vm._renderProxy = vm;
}
如果是生產環境vm._renderProxy 就是 vm實例,開發環境的話我們會轉到proxy.js中。
在這裏面,如果瀏覽器支持proxy,那麼就是利用proxy做了個攔截(並不是大家理解的響應式),攔截的目的是做一些提醒,典型的就是我們因爲粗心大意把data中的鍵寫錯了:
`Property or method "${key}" is not defined on the instance but ` +
'referenced during render. Make sure that this property is reactive, ' +
'either in the data option, or for class-based components, by ' +
'initializing the property. ' +
這裏會報錯其實就是利用proxy,傳入了一個getHander的函數幫助我們完成的,當然如果沒有proxy特性那麼也沒法做這個優化,就和生產環境的效果是一樣的了。
最後我們再回到render.js中的renderMixin裏面,我們剛剛走完了try的部分,沒問題的話會走到最後的finally部分,其中又有一個我們常見的錯誤:
"Multiple root nodes returned from render function. Render function " +
"should return a single root node.",
這個報錯的原因就是vnode還是一個array,以爲着template內部節點不唯一。
接下來
下一節我們開始看VNode。