vue2数据渲染(3)- _render

142 阅读1分钟

vue有一个私有的实例方法 _render ,这个方法获得vnode

src/core/instance/render.ts

export function renderMixin(Vue: typeof Component) {
  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype)

  Vue.prototype.$nextTick = function (fn: (...args: any[]) => any) {
    return nextTick(fn, this)
  }

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

    if (_parentVnode && vm._isMounted) {
      vm.$scopedSlots = normalizeScopedSlots(
        vm.$parent!,
        _parentVnode.data!.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
      if (vm._slotsProxy) {
        syncSetupSlots(vm._slotsProxy, 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.
      setCurrentInstance(vm)
      currentRenderingInstance = vm
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e: any) {
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (__DEV__ && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(
            vm._renderProxy,
            vm.$createElement,
            e
          )
        } catch (e: any) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      currentRenderingInstance = null
      setCurrentInstance()
    }
    // if the returned array contains only a single node, allow it
    if (isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if (__DEV__ && 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
  }
}

在最开始的文件 src/core/instance/index.ts 中,调用了 renderMixin(Vue),给vue扩展了实例方法_render,

先从 options 中拿到 render, const { render, _parentVnode } = vm.$options,这个render可以是用户自己写的,也可以是通过编译生成的

然后最关键的是 render 方法的调用来获得 vnode,

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

vm._renderProxy是一个上下文对象,在生产环境中就是vm,在开发环境中是一个proxy对象, 在 src/core/instance/init.ts 的initMixin中,

if (__DEV__) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }

用户在写render函数的时候,接收一个参数,就是createElement,

src/core/instance/render.ts

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
  // @ts-expect-error
  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.
  // @ts-expect-error
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
...
}

initMixin 中执行 initRender,都是调用 createElement,vm._c是给编译用的,vm.$createElement是给用户手写render用的

总结:

  1. 总之Vue.prototype._render要返回一个vnode,
  2. 首先是拿到render,调用它生成vnode,调用render,通过vm.$createElement,最后通过调用 createElement 方法生成vnode,
  3. 下一篇分析createElement怎么生成vnode