vue的原始碼學習之五——4.資料驅動(render)
-
介紹
版本:2.5.17。
我們使用vue-vli建立基於Runtime+Compiler的vue腳手架。
學習文件:https://ustbhuangyi.github.io/vue-analysis/data-driven/render.html
-
對接上一節(vue的原始碼學習之五——3.資料驅動(Vue 例項掛載的實現)
在上一節中我們提到了在src/core/instance/lifecycle.js 中通過渲染Watcher實時去監測呼叫updateComponent方法,從而實現的頁面實時渲染,vm._render()主要是生成的VNode(虛擬DOM)
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
-
vm._render
vue 的
_render
方法是例項的一個私有方法,它用來把例項渲染成一個虛擬 Node。它的定義在src/core/instance/render.js
Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options // reset _rendered flag on slots for duplicate slot check if (process.env.NODE_ENV !== 'production') { for (const key in vm.$slots) { // $flow-disable-line vm.$slots[key]._rendered = false } } if (_parentVnode) { vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject } // set parent vnode. this allows render functions to have access // to the data on the placeholder node. vm.$vnode = _parentVnode // render self let vnode try { vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { handleError(e, vm, `render`) // return error render result, // or previous vnode to prevent render error causing blank component /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { if (vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e) } catch (e) { handleError(e, vm, `renderError`) vnode = vm._vnode } } else { vnode = vm._vnode } } else { vnode = vm._vnode } } // return empty vnode in case the render function errored out if (!(vnode instanceof VNode)) { if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ) } vnode = createEmptyVNode() } // set parent vnode.parent = _parentVnode return vnode }
1、拿到vue例項引數中的render函式,這個render函式可以使使用者自己寫的,也可以是template編譯成的render函式
const { render, _parentVnode } = vm.$options
2、去呼叫這個render函式
try { vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { }
vm.$createElement建立虛擬的DOM ,它的定義
export function initRender (vm: Component) { // 編譯時建立VNode的方法 vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // 手寫render函式的時候建立VNode的方法 vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) }
其中initRender這個函式在new Vue的時候會去呼叫在src/core/instance/init.js中可以看到。
對於vm.$createElement我們可舉一個例子
<div id="app"> {{ message }} </div>
相當於我們編寫如下
render
函式:render: function (createElement) { return createElement('div', { attrs: { id: 'app' }, }, this.message) }
再回到
_render
函式中的render
方法的呼叫:vnode = render.call(vm._renderProxy, vm.$createElement)
可以看到,
render
函式中的createElement
方法就是vm.$createElement
方法:export function initRender (vm: Component) { // ... // bind the createElement fn to this instance // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize // internal version is used by render functions compiled from templates vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // normalization is always applied for the public version, used in // user-written render functions. vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) }
實際上,vm.$createElement
方法定義是在執行 initRender
方法的時候,可以看到除了 vm.$createElement
方法,還有一個 vm._c
方法,它是被模板編譯成的 render
函式使用,而 vm.$createElement
是使用者手寫 render
方法使用的, 這倆個方法支援的引數相同,並且內部都呼叫了 createElement
方法。
-
總結
vm._render
最終是通過執行 createElement
方法並返回的是 vnode
,它是一個虛擬 Node。Vue 2.0 相比 Vue 1.0 最大的升級就是利用了 Virtual DOM。