Vue2源码解析(四)

260 阅读1分钟

render 方法

Vue的_render方法是实例的一个私用方法,它是用来渲染一个虚拟Node,它定义在src/core/instance/render.js文件中:

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 (!(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,下面的模板:

<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._c方法,它是被模板编译成的render函数使用的,而vm.$createElement方法是用户手写render方法使用的,这两个方法参数都相同,且都调用到了createElement方法。

总结

vm._render最终是通过执行createElement方法并返回的vnode虚拟Node。下一篇就简单说下vue中Virtual DOM

vue2源码解析(三)

vue2源码解析(二)

vue2源码解析(一)