注册
在Vue
中组件注册分为全局注册和局部注册
全局注册
Vue.component('my-component-name', { /* ... */ })
- 组件名
'my-component-name'
- 配置项
{ /* ... */ }
局部注册
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
无论时全局组件还是局部组件一定要注册后才能使用。
Vue.component
在src/core/global-api/assets.js
中,initAssetRegisters
方法遍历ASSET_TYPES
,并定义相关方法
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
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
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
// src/shared/constants.js
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
也就是说通过initAssetRegisters
方法,给Vue
构造函数添加了component,directive,filter
方法,而initAssetRegisters
会在src/core/global-api/index.js
中的initGlobalAPI
中进行调用。通过
this.options[type + 's'][id] = definition
把定义的全局组件添加到Vue.options.components
对象上。
可以看到Vue.component
接受两个参数,id
为字符串类型,可以作为第二个参数definition
的name
属性。重点看看下面这句代码
definition = this.options._base.extend(definition)
上面这行代码等价于
definition = Vue.extend(definition)
从前面的文章分析中我们知道,Vue.extend
最终会返回组件构造器函数,并且在extend
方法中调用mergeOptions
方法对配置进行合并。
Sub.options = mergeOptions(
Super.options,
extendOptions
)
在组件实例化的时候,在initInternalComponent
方法中进行配置合并
const opts = vm.$options = Object.create(vm.constructor.options)
也就是说此时vm.$options.components
的对象中包含了我们定义的全局组件。
比如vm.$options.components['my-component-name'] = definition
在创建组件vnode
的过程中,_createElement
方法中会执行以下逻辑
Ctor = resolveAsset(context.$options, 'components', tag)
我们看看resolveAsset
方法
// src/core/util/options.js
/**
* Resolve an asset.
* This function is used because child instances need access
* to assets defined in its ancestor chain.
*/
export function resolveAsset (
options: Object,
type: string,
id: string,
warnMissing?: boolean
): any {
/* istanbul ignore if */
if (typeof id !== 'string') {
return
}
const assets = options[type]
// check local registration variations first
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
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
}
- 通过
const assets = options[type]
拿到当前实例vm.$options
对象上的components
- 然后判断
tag
是否存在于components
对象中,在判断的过程中,首先用传入的参数进行判断,如果没有尝试将tag
转化为驼峰,最后转化为首字母大写进行组件是否存在判断。
如果resolveAsset
方法有返回值,在_createElement
方法中接着调用createComponent
方法生成为组件vnode
。
通过上面的分析,我们应该可以了解到
Vue.component
注册全局组件背后的机制,这里进行了多次merge options,首先将Vue.options
合并到子类的Super.options
对象上,在组件实例化的时候,还会进行配置项的合并,最终在组件实例的vm.$options
属性上存在着components
对象,而components
中有我们定义的全局组件
局部注册
components:{
'component-a': ComponentA,
'component-b': ComponentB
}
通过前面的文章分析我们可以清楚的了解到局部注册这种方式在组件实例的vm.$options.components
对象上有我们定义的局部组件
小结
全局注册和局部注册其实实现思路一致,都是通过配置合并保证在组件实例的$options
对象上有我们定义的全局或局部组件,以确保能通过resolveAsset
方法,最终调用createComponent
方法生成组件vnode
。