# components是如何渲染的

mergeOptions中关于components、filters、directives的合并,以及自定义组件的查找方式。

# 组件注册

# 全局组件

全局注册,是将组件挂载到Vue.options.components上,比如下方的aaa组件:

当我们调用Vue.component来注册全局组件时。

该方法是initAssetRegisters执行时定义的全局方法,代码位置:core/global-api/asset.js

let Sub = Vue.component('aaa', {
    name: 'aaa',
    template: '<span>{{ name }}</span>',
    data() {
        return {
            name: 'aaa'
        };
    }
});
1
2
3
4
5
6
7
8
9

内部会调用Vue.extend方法,返回一个Sub子类构造函数。同时,将该Sub子类存放到Vue..options.components上。

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   * ASSET_TYPES:[ 'component', 'directive', 'filter']
   * 主要是给Vue添加静态方法:Vue.component, Vue.directive, Vue.filter
   * Vue.components: 调用的是Vue.extend方法
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          // 调用Vue.extend,返回Sub子类
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        // this就是Vue,
        // 将Sub子类,存放到Vue.options.components上
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}
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

如下图所示:Vue.options.components全局组件上就存放了aaa这个组件,其值为Sub子类。

image-20210916154921595

另外,在Vue.extend内部,它会调用mergeOptions方法,将Vue.options与传入的extendOptions合并到Sub.options上。

Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    // 子类构造函数
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    // Sub继承自Vue
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    // 合并Vue.options、extendOptions到Sub.options
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    // Sub.options.components上挂载自身, 用于递归组件调用,
    // 组件内部可通过Sub.options.components['name']找到自身,例如:Sub.options.components['App'] = Sub
    if (name) { // 递归组件必须有name属性
      Sub.options.components[name] = Sub
    }
    return Sub
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

此时,我们输出Vue.extend阶段返回的definition(Sub子类)的options值:可以看到,它的components上,也存放了aaa这个组件(主要用于递归组件),同时,其**原型链**上还挂载了全局的KeepAlive、Transition、TransitionGroup组件。

image-20210916162239413

# 局部组件注册

局部组件,是将要注册的组件挂载到components选项上,如下方的bbb组件:

<template>
  <div id="app">
    <p>{{ name }}</p>
  </div>
</template>

<script>
export default {
  name: 'appVue',
  data () {
    return {
      name: 'app home'
    }
  },
  components: {
      'bbb': {
          name: 'bbb',
          template: '<span>bbb</span>'
      }
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

当我们挂载子组件时,在子组件初始化_init阶段,它调用initInternalComponent函数时,其内部将vm.$options与其父类构造函数的options,通过原型链关联起来。

function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  // ...
  const opts = vm.$options = Object.create(vm.constructor.options);
  // ...
}
1
2
3
4
5

即,vm.$options的原型,指向了Sub.options。

// 伪代码
vm.$options.__proto__ == vm.constrctor.options;
1
2

# components原型链创建过程

1、Sub.options.components原型,指向Vue.options.components

​ 该阶段发生在Vue.extend的mergeOptions执行阶段:当我们合并components时,会调用mergeAssets方法,内部通过Object.create创建Sub.options.components与Vue.options.components的原型关联。

// core/util/options.js  187行 mergeAssets方法
const res = Object.create(parentVal || null);

// 伪代码
// Sub.options.components.__proto == Super.options.components; // true
1
2
3
4
5

2、vm.$options原型,指向Sub.options

​ 该阶段发生在子组件_init阶段,当执行initInternalComponent函数时,创建vm.$options与Sub.options的原型关联

// core/instance/init.js 
const opts = vm.$options = Object.create(vm.constructor.options)
1
2

整个过程如下图所示:

1、initInternalComponent发生在子组件初始阶段;

2、Sub.options与Vue.options原型关联,发生在Vue.extend阶段;

image-20210914172832741

# 渲染组件

渲染包括以下几步:

  • render阶段:调用render函数,创建vnode;
  • update阶段:调用patch函数,对比vnode并创建真实dom;

当我们调用_createElement创建自定义组件vnode时,如果tag是一个自定义组件名,例如'aaa',内部调用resolveAsset查找'aaa'对应的自定义组件。

  • 先从vm.$options.components上查找,然后到Sub.options.components,然后到全局的Vue.options.components查找。
// 创建vnode
export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  
  let vnode, ns
  // 判断tag类型
  if (typeof tag === 'string') {
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // resolveAsset(core/utils/options.js 465行),从'context.$options'的'components'上查找组件对象(局部注册组件)
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // 3: tag是一个未知的元素
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // direct component options / constructor
    // 组件vnode(此时tag是一个options对象),类似这种render: h => h(App)
    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

resolveAsset在查找components时,就2步:

  • 先从自身查找
  • 找不到,依据原型链查找
export function resolveAsset (
  options: Object,
  type: string,
  id: string,
  warnMissing?: boolean
): any {
  /* istanbul ignore if */
  if (typeof id !== 'string') {
    return
  }
  // 从options上寻找'type'属性,例如:通过一个字符串,从options.components获取自定义的components组件对象
  const assets = options[type]
  // check local registration variations first
  // 1 优先从本地注册的变量查找
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // fallback to prototype chain
  // 2 找不到,则根据原型链查找Vue.options.components['compName']
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    )
  }
  return res
}
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