render
Vue 的?_render?方法是實例的一個私有方法,它用來把實例渲染成一個虛擬 node( DOM? 當初看某個文檔 還以為是node.js 一臉懵逼啊)。它的定義在?src/core/instance/render.js?文件中
// Vue 的 _render 方法是實例的一個私有方法,它用來把實例渲染成一個虛擬 Node
? // 這段代碼最關鍵的是 render 方法的調用,我們在平時的開發工作中手寫 render 方法的場景比較少,而寫的比較多的是 template 模板,在之前的 mounted 方法的實現中,
? // 會把 template 編譯成 render 方法,但這個編譯過程是非常復雜的
? 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
? }
// 這段代碼最關鍵的是 render 方法的調用,我們在平時的開發工作中手寫 render 方法的場景比較少,
? // 而寫的比較多的是 template 模板,在之前的 mounted 方法的實現中,會把 template 編譯成 render 方法,但這個編譯過程是非常復雜的
render 的 createElement
在 Vue 的官方文檔中介紹了?render?函數的第一個參數是?createElement,那么結合之前的例子:
? {{ message }}
</div>
相當于我們編寫如下?render?函數:
render: function (createElement) {
? return createElement('div', {
???? attrs: {??
??????? id: 'app'
????? },???????
? }, this.message)
}
// 實際上,vm.$createElement 方法定義是在執行 initRender 方法的時候,可以看到除了 vm.$createElement 方法,
// 還有一個 vm._c 方法,它是被模板編譯成的 render 函數使用,而 vm.$createElement 是用戶手寫 render 方法使用的,
// 這倆個方法支持的參數相同,并且內部都調用了 createElement 方法。
export function initRender (vm: Component) {
? vm._vnode = null // the root of the child tree
? vm._staticTrees = null // v-once cached trees
? const options = vm.$options
? const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
? const renderContext = parentVnode && parentVnode.context
? vm.$slots = resolveSlots(options._renderChildren, renderContext)
? vm.$scopedSlots = emptyObject
? // 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)
? // $attrs & $listeners are exposed for easier HOC creation.
? // they need to be reactive so that HOCs using them are always updated
? const parentData = parentVnode && parentVnode.data
? /* istanbul ignore else */
? if (process.env.NODE_ENV !== 'production') {
? ? defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
? ? ? !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
? ? }, true)
? ? defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
? ? ? !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
? ? }, true)
? } else {
? ? defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
? ? defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
? }
}
vm._render?最終是通過執行?createElement?方法并返回的是?vnode,它是一個虛擬 Node。Vue 2.0 相比 Vue 1.0 最大的升級就是利用了 Virtual DOM。因此在分析?createElement?的實現前,我們先了解一下 Virtual DOM 的概念。
Virtual DOM
Virtual DOM 大部分人都不會陌生,它產生的前提是瀏覽器中的 DOM 是很“昂貴"的?我們可以簡單的把一個簡單的 div 元素的屬性都打印出來
真正的 DOM 元素是非常龐大的,因為瀏覽器的標準就把 DOM 設計的非常復雜。當我們頻繁的去做 DOM 更新,會產生一定的性能問題。
而 Virtual DOM 就是用一個原生的 JS 對象去描述一個 DOM 節點,所以它比創建一個 DOM 的代價要小很多。在 Vue.js 中,Virtual DOM 是用?VNode?這么一個 Class 去描述,它是定義在?src/core/vdom/vnode.js?中的。
實際上 Vue.js 中 Virtual DOM 是借鑒了一個開源庫?snabbdom?的實現,然后加入了一些 Vue.js 特色的東西。
export default class VNode {
? tag: string | void;
? data: VNodeData | void;
? children: ?Array<VNode>;
? text: string | void;
? elm: Node | void;
? ns: string | void;
? context: Component | void; // rendered in this component's scope
? key: string | number | void;
? componentOptions: VNodeComponentOptions | void;
? componentInstance: Component | void; // component instance
? parent: VNode | void; // component placeholder node
? // strictly internal
? raw: boolean; // contains raw HTML? (server only)
? isStatic: boolean; // hoisted static node
? isRootInsert: boolean; // necessary for enter transition check
? isComment: boolean; // empty comment placeholder?
? isCloned: boolean; // is a cloned node?
? isOnce: boolean; // is a v-once node?
? asyncFactory: Function | void; // async component factory function
? asyncMeta: Object | void;
? isAsyncPlaceholder: boolean;
? ssrContext: Object | void;
? fnContext: Component | void; // real context vm for functional nodes
? fnOptions: ?ComponentOptions; // for SSR caching
? fnScopeId: ?string; // functional scope id support
? constructor (
? ? tag?: string,
? ? data?: VNodeData,
? ? children?: ?Array<VNode>,
? ? text?: string,
? ? elm?: Node,
? ? context?: Component,
? ? componentOptions?: VNodeComponentOptions,
? ? asyncFactory?: Function
? ) {
? ? this.tag = tag
? ? this.data = data
? ? this.children = children
? ? this.text = text
? ? this.elm = elm
? ? this.ns = undefined
? ? this.context = context
? ? this.fnContext = undefined
? ? this.fnOptions = undefined
? ? this.fnScopeId = undefined
? ? this.key = data && data.key
? ? this.componentOptions = componentOptions
? ? this.componentInstance = undefined
? ? this.parent = undefined
? ? this.raw = false
? ? this.isStatic = false
? ? this.isRootInsert = true
? ? this.isComment = false
? ? this.isCloned = false
? ? this.isOnce = false
? ? this.asyncFactory = asyncFactory
? ? this.asyncMeta = undefined
? ? this.isAsyncPlaceholder = false
? }
? // DEPRECATED: alias for componentInstance for backwards compat.
? /* istanbul ignore next */
? get child (): Component | void {
? ? return this.componentInstance
? }
}
其實 VNode 是對真實 DOM 的一種抽象描述,它的核心定義無非就幾個關鍵屬性,標簽名、數據、子節點、鍵值等,其它屬性都是都是用來擴展 VNode 的靈活性以及實現一些特殊 feature 的。由于 VNode 只是用來映射到真實 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常輕量和簡單的。
Virtual DOM 除了它的數據結構的定義,映射到真實的 DOM 實際上要經歷 VNode 的create、diff、patch 等過程。那么在 Vue.js 中,VNode 的create 是通過之前提到的?createElement?方法創建的。