• 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) // <------
      }
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    如上代码中注释所述,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) { ... }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    两种 VNode 对象对比:

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

    总结:

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

  • 相关阅读:
    平衡三进制分布式计算
    推荐几个制作svg的工具
    Zabbix与乐维监控对比分析(二)——Agent管理、自动发现、权限管理
    ubuntu 添加、删除用户,修改用户名称,修改主机名
    UG NX二次开发(C#)-获取UI中选择对象的handle值
    500G+顶级学术及大厂数据集开放下载,科研学术赛题开发指南
    深度学习(五)——DatadLoader的使用
    政安晨【零基础玩转各类开源AI项目】:解析开源项目:Champ 利用三维参数指导制作可控且一致的人体图像动画
    腾讯99公益日-券券松鼠❤
    嵌入式学习(标准IO)
  • 原文地址:https://blog.csdn.net/dclnet/article/details/126174115