render

Vue’s _render method is a private method of the instance that renders it as a virtual node. He defined in SRC/core/instance/render. The js file

  Vue.prototype._render = function () :VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }

    // 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 {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm
      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' && 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
      }
    } finally {
      currentRenderingInstance = null
    }
    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]}// return empty vnode in case the render function errored out
    if(! (vnodeinstanceof 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
  }
Copy the code

The most critical part of this code is the call of the Render method, we in the usual development work handwritten render method scene is less, and write more is the template template, in the previous mounted method implementation, template will be compiled into the render method, But this compilation process is very complicated.

The first argument to the render function is createElement, as described in the official Vue documentation.

<div id='app'>
    {{message}}
</div>
Copy the code

Equivalent to writing the render function as follows:

render:function (createElement) {
    return createElement('div', {
        attrs: {
            id: "app"}},this.message)
}
Copy the code

Call the render method in the _render function:

vnode = render.call(vm._renderProxy, vm.$createElement)
Copy the code

$createElement vm.$createElement vm.$createElement vm.

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)}}Copy the code

In fact, the vm. CreateElement method is defined when the initRender method is executed, and you can see that except for the vm. CreateElement method is defined when the initRender method is executed, In addition to the vm.createElement method defined when executing the initRender method, you can see that in addition to the vm.createElement method, there is also a vm._c method, which is used by the render function compiled from the template. While vm.$createElement is used by the user’s handwritten Render method, the two methods support the same parameters and both call createElement internally.

conclusion

Vm. _render finally returns vNode, which is a virtual Node, by executing the createElement method. The biggest upgrade to Vue2.0 is VirtualDOM, so let’s look at the concept of VirtualDOM before we analyze the implementation of createElement

Virtual DOM

The concept of Virtual DOM is familiar to most people. The premise is that the DOM in the browser is very expensive. For a more intuitive feeling, we can simply print out the attributes of a simple DIV, as shown in the figure below:

As you can see, the actual DOM elements are very large, because browser standards make the DOM very complex. When we do frequent DOM updates, there will be certain performance issues.

VirtualDOM uses a native JS object to describe a DOM node, so it is much less expensive than creating a DOM. In Vue. Js, VirtualDOM is bought with a Class called VNode, which is defined in SRC /core/vdom/vnode.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 nodesfnOptions: ? ComponentOptions;// for SSR caching
  devtoolsMeta: ?Object; // used to store functional render context for devtoolsfnScopeId: ? 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
  }
}
Copy the code

As you can see, the definition of VirtualDOM in vue.js is a little more complicated because it contains many of the features of vue.js. VirtualDOM in Vue. Js is actually a VirtualDOM implementation borrowed from the open source library Snabbdom, and then added some features of Vue. I recommend that you read the source code of this library if you want to know more about VirtualDOM of vue.js, because it is much simpler and more pure.

conclusion

In fact, VNode is an abstract description of the real DOM. Its core definition is nothing more than a few key attributes, such as tag name, data, child nodes, key values, etc. In fact, attributes are used to extend the interest of VNode and achieve some special features. Since VNode is only used to map renderings of the real DOM and does not need to include methods to manipulate the DOM, it is very lightweight and simple.

In addition to the definition of Virtual DOM’s data structure, the mapping to the real DOM actually goes through the process of VNode create, diff, patch and so on. In vue.js, the VNode create is created using the createElement method mentioned earlier.