# 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'
};
}
});
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
}
}
})
}
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子类。
另外,在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
}
}
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组件。

# 局部组件注册
局部组件,是将要注册的组件挂载到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>'
}
}
}
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);
// ...
}
2
3
4
5
即,vm.$options的原型,指向了Sub.options。
// 伪代码
vm.$options.__proto__ == vm.constrctor.options;
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
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)
2
整个过程如下图所示:
1、initInternalComponent发生在子组件初始阶段;
2、Sub.options与Vue.options原型关联,发生在Vue.extend阶段;

# 渲染组件
渲染包括以下几步:
- 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)
}
// ...
}
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
}
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
← Vue.mixin原理 render函数 →