vue内部内置了很多好用的组件可以直接拿来使用,自己自定义的组件则需要先定义后使用。
组件注册可以分为全局注册和局部注册。
1、全局注册
import Vue from "vue";
import App from "./App";
// 全局注册
import globalComponent from "@/components/globalComponent";
Vue.component("globalComponent", globalComponent);
const app = new Vue({
el: "#app",
render(h) {
return h(App);
}
});
这里的globalComponent可以是手写的,也可以是引入并通过vue-loader处理过的,通过Vue.component("globalComponent", globalComponent)的方式注册的页面中所有的页面都可以直接使用
<template>
<div>
<globalComponent></globalComponent>
</div>
</template>
那么Vue.component是什么时候定义的?
答:在initGlobalAPI(Vue)时定义的。
在initGlobalAPI(Vue)中有方法initAssetRegisters(Vue):
import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'
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
}
}
})
}
其中ASSET_TYPES在shared/constants.js中:
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
ASSET_TYPES通过循环的方式给Vue上分别挂载全局组件注册、全局指令和全局过滤器方法,这里主要看组件的注册,当符合type === 'component'并且definition是普通对象的时候,给将this.options._base.extend(definition)赋值给definition,this.options._base指的是Vue,extend是将当前的组件转化成组件构造函数。可以参考juejin.cn/post/712909…
最后,this.options[type + 's'][id] = definition相当于Vue.options.components[组件ID] = definition。
完成组件的注册后,在构造vNode阶段就可以使用了,在_createElement函数中有一段逻辑是:
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
warn(
`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
context
)
}
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
当前tag是sting格式,在当前条件下,会执行到isDef(Ctor = resolveAsset(context.$options, 'components', tag)的判断,这里的context.$options是在组件构造函数实例化this._init执行初始化逻辑的方法initInternalComponent(vm, options)中进行赋值的:
function initInternalComponent (vm, options) {
var opts = vm.$options = Object.create(vm.constructor.options);
// doing this because it's faster than dynamic enumeration.
var parentVnode = options._parentVnode;
opts.parent = options.parent;
opts._parentVnode = parentVnode;
var vnodeComponentOptions = parentVnode.componentOptions;
opts.propsData = vnodeComponentOptions.propsData;
opts._parentListeners = vnodeComponentOptions.listeners;
opts._renderChildren = vnodeComponentOptions.children;
opts._componentTag = vnodeComponentOptions.tag;
if (options.render) {
opts.render = options.render;
opts.staticRenderFns = options.staticRenderFns;
}
}
var opts = vm.$options = Object.create(vm.constructor.options)将当前实例构造函数中的options赋值给实例的$options。
继续看isDef(Ctor = resolveAsset(context.$options, 'components', tag)),如果满足则会按照组件的方式创建vNode,resolveAsset方法如下:
/**
* 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
}
options[type]就是options['components'],用来获取到当前options或者祖先链路上的components数组后再通过assets[id]、assets[camelizedId]和assets[PascalCaseId]的方式获取组件,即分别通过原始id、转换成小驼峰和大驼峰后的id去获取组件的构造函数。在全局注册的例子中,可以获取到assets[id],即可返回res,进入条件执行并vnode = createComponent(Ctor, data, context, children, tag)。
2、局部注册
在组件App.vue中:
<template>
<div>
<partialComponent></partialComponent>
</div>
</template>
<script>
import partialComponent from '@/components/partialComponent'
export default {
components: {
partialComponent,
}
}
</script>
这里定义了components,在创建vNode的时候就可以通过const assets = options[type]获取components,然后通过if (hasOwn(assets, id)) return assets[id]判断条件存在并返回,进而进入条件执行并vnode = createComponent(Ctor, data, context, children, tag)。
小结
全局组件一次注册在页面中可以到处使用,局部注册只在当前页面中可以用。一般使用率比较高的组件可以通过全局注册,只在当前页面中的业务组件可以通过局部注册完成。