Vue2源码学习笔记 - 20.渲染与编译—createComponent 创建组件VNode

上一节我们研究了从 render 函数生成 vnode 的流程,这是通过 createElement 函数去实现的,那么在这其中遇到组件标签时,它实际又是调用 createComponent 函数去创建组件 vnode,或许叫组件占位 vnode 更准确,因为它并非渲染时 DOM 元素的 vnode。我们来看看它的内部实现。

调用场景:

createComponent 函数是在上一节介绍的 _createElement 函数中被调用的,如下代码。传入 _createElement 的参数 tag 为普通 HTML 元素标签则创建其元素 VNode 对象,如果为组件选项组件构造函数则调用 createComponent 函数创建组件占位 VNode 对象。
file: /src/core/vdom/create-element.js

export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  // 响应式的 data 直接返回空注释的 vnode
  if (isDef(data) && isDef((data: any).__ob__)) {
    ...
    return createEmptyVNode()
  }
  // 如有 is 属性则把其值当组件名赋给 tag
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // 无tag 则创建空注释 vnode
    return createEmptyVNode()
  }
  ...

  // 扁平化 children
  ...
  
  let vnode, ns
  // tag 为字符串
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      ...
      // 创建 HTML\svg 标签的 vnode
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // tag 为组件,调用 createComponent创建 vnode
      vnode = createComponent(Ctor, data, context, children, tag) // <-----
    } else {
      // 除了上面两种情况的 vnode
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // tag 为 组件选项 或 组件构造函数,调用 createComponent创建 vnode
    vnode = createComponent(tag, data, context, children) // <------
  }
  ...
}

createComponent:

我们来看看 createComponent 这个函数的源码
file: /src/core/vdom/create-component.js

export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData, context: Component,
  children: ?Array<VNode>, tag?: string
): VNode | Array<VNode> | void {
  // 1、处理生成组件构造函数 ================================
  // Ctor 参数为空直接退出; baseCtor 即是 Vue
  if (isUndef(Ctor)) { return }
  const baseCtor = context.$options._base

  // 如果 Ctor 是选项对象, Vue.extend 扩展为 Vue 子类
  if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) }

  // Ctor 如不是 组件构造函数 则退出
  if (typeof Ctor !== 'function') { return }

  // 处理异步组件,生成异步组件构造函数 Ctor
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      return createAsyncPlaceholder(asyncFactory,data,context,
        children,tag)
    }
  }
  // 2、处理data\props\事件监听器 ==========================
  data = data || {}
  // 在创建组件构造函数后 应用全局混合时 解析构造函数选项
  // 更新后的父类 options 合并到 Ctor.options   
  resolveConstructorOptions(Ctor)

  // 将组件 v-model 数据转换为 props 和 events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }

  // 提取 props的值
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  // 函数式组件则调用 createFunctionalComponent创建 vnode
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // on 监听器 赋 listeners 
  const listeners = data.on
  // 原生事件监听器 赋 data.on
  data.on = data.nativeOn

  // 抽象组件(如 keep-alive)除了 props、listeners、slot 外不保留任何东西
  if (isTrue(Ctor.options.abstract)) {
    const slot = data.slot
    data = {}
    if (slot) { data.slot = slot }
  }
  // 3、安装组件钩子函数 ==============================
  installComponentHooks(data)
  
  // 4、创建组件 占位vnode ==============================
  const name = Ctor.options.name || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  ...
  return vnode
}

如上代码中注释所述,createComponent 处理大致可分为 4 步:

1、处理组件构造函数,传入选项对象则 Vue.extend 扩展生成,异步组件则调用异步生成,处理后 Ctor 必须是构造函数。

2、更新合并组件 options,处理组件 data\props 等,这个过程中还会判断是不是函数式组件,是则直接创建 vnode 并返回,然后中止后续执行。

3、安装组件钩子函数,钩子函数存于 data.hook 中,用于后续实例化组件和渲染等等场合,其中函数类似:

var componentVNodeHooks = {
  init: function init (vnode, hydrating) { ... },
  prepatch: function prepatch (oldVnode, vnode) { ... },
  insert: function insert (vnode) { ... },
  destroy: function destroy (vnode) { ... }
};

4、创建组件的占位 vnode,传入前面处理过的一系列数据创建占位 vnode 对象,其中 tag 为固定拼接的字符串,一般类似于 vue-component-(数字)-(组件名),children 为空,然后返回该占位 vnode 对象。

两种 VNode 对象对比:

下图简单列举这两类 VNode 对象的部分不同属性:
vue 两种 VNode 对象对比

总结:

如上所述,createComponent 函数先处理组件的构造函数,然后整理相关数据,再安装钩子函数,最后创建组件占位 vnode 对象。值得注意的是,这个 vnode 对象并不是实际渲染时 DOM 元素对应的 vnode,正如它的名字一样,它更像是用来占位的。其中的钩子函数在渲染时会创建组件实例,这个后面我们继续研究学习。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值