【Vue源码】4._render

352 阅读1分钟

_render

3.Vue 实例挂载的实现 中我们看到最后 mountComponent 方法调用的是 _render_update 这两个方法。

_render是实例的私有方法,作用是把实例渲染成一个虚拟 DOM,定义在 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;
    }
  }
  // 生成空的vnode进行兜底
  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$options 中取出,并执行

vnode = render.call(vm._renderProxy, vm.$createElement);

不过一般我们是用 template 模板来写结构,官方也提供了 render 的方法。

<div id="app">{{ message }}</div>

render 的写法相当于

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

再回到 render 的调用

vnode = render.call(vm._renderProxy, vm.$createElement);

我们可以看到执行 render 方法中的 createElement 参数就是 vm.$createElement,那么 vm.$createElement 方法在哪呢? 实际上 vm.$createElement 方法是定义在 initRender 方法上的。

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._cvm.$createElement 的区别为,vm._c 是被模板编译成的 render 函数使用,而 vm.$createElement 是用户手写 render 方法使用的, 这俩个方法支持的参数相同,并且内部都调用了 createElement 方法。createElement 具体做什么在后面会讲到。

总结

04.数据驱动-_render.png

vm._render 最终是执行 createElement 方法并返回 VNode,他是 Virtual DOMVue2.0 的一大亮点就是使用了 Virtual DOM,因此在分析 createElement 之前,我们先了解一下 Virtual DOM 的概念。

【Vue源码】6.createElement

源码分析 GitHub 地址