Vue.js 源碼學習筆記3 數據驅動(中)

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?方法創建的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容