Skip to content

render

vm._render()

它的作用就是把实例渲染成虚拟节点vnode,定义在src/core/instance/render.js文件中。

js
Vue.prototype._render = function (): VNode {
  const vm: Component = this
  // 获取用户定义的render 或者模版渲染的render
  const { render, _parentVnode } = vm.$options
  ...
  try{
    // vm.$createElement 就是一般传入的参数h,生成虚拟dom
    vnode = render.call(vm._renderProxy, vm.$createElement)
  }
  ...
}

首先获取模版编译生成的render函数或者用户自定义的render函数,重点render函数的调用, 用户自定义的render函数一般如下,这里的h就是vm.$createElement方法。

js
render: function (h) {
    return h(
    )
  },

vm.$createElement方法定义在函数initRender

js
export function initRender(vm: Component) {
  // internal version is used by render functions compiled from templates
  // 对编译生成的 render 函数进行渲染的方法
  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.
  // 对手写 render 函数进行渲染的方法,也就是 h
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}

这里不论是模版编译生成的render函数,还是用户传入的render函数,内部都调用了createElement函数,只是最后一个标识参数不同。createElement函数将会生成 vnode

createElement()

js
export function createElement(
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode | Array<VNode> {
  // 是否是数组(子节点)或原始值(标签中的内容)
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  // 用户传入的render,为了后续处理children
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}

它的主要作用是先处理一下参数,然后内部再调用_createElement()生成具体的 Vnode, 传递给_createElement函数的参数有:

  • context 表示 VNode 的上下文环境
  • tag 标签,类型为字符串或 Component
  • data VNode的数据
  • children 当前 VNode的 子节点,会经过规范化处理,子节点也要处理成 VNode 格式
  • normalizationType,子节点规范的类型,会依据render函数是编译生成的(SIMPLE_NORMALIZE)还是用户手写的(ALWAYS_NORMALIZE)

_createElement()中,先规范化children子节点,然后根据不同tag类型创建VNode。

js
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    // 是否是html的保留标签
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // component
      // tag为已注册的组件
      // 查找自定义组件构造函数的声明
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // direct component options / constructor
    // tag为组件或组件构造函数
    vnode = createComponent(tag, data, context, children)
  }

先判断 tag 的类型,

  • string 类型,则继续判断如果为内置的保留节点,则直接创建一个普通VNode; 如果是一个已注册的组件名,则通过createComponent 创建一个组件类型的 VNode,否则创建一个未知的标签的 VNode
  • Component 类型,直接调用 createComponent 创建一个组件类型的 VNode 节点

总结

vm._render执行完成生成 VNode 后,需要渲染成真实Dom,接下来就由vm._update来将VNode渲染成真实Dom