大家平时用vue开发项目的时候,大部分的时间就是在写组件吧,而大家都已经习惯了single-file components (单文件组件),写起来已经轻车熟路,但不知道大家有没有想过一个问题:一个组件是怎么被注册的?或者说vue是怎么知道他是一个组件的?
答案其实也很简单,只要在components对象中声明就行了,但具体是怎么注册的呢,声明这步好像仅仅是给组件起了个名字,我在开发组件的时候,比如ComponentA.vue,我给他添加一个属性name:ComponentA,这样不是就能用了吗?有人会说了这个属性不是必选的,是的,如果不写,那我默认文件名不就可以了,所以声明一个组件的内部逻辑是什么呢?一个组件到底是一个纯粹的对象,还是一个函数呢?带着这些问题,我们一起探索下。
不知道大家有没有遇到过开发一个项目不需要webpack,vue-loader这些工具的情况,比方说直接引入vue:
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
我们如何注册一个组件呢?
官方的API:Vue.component
Vue.component('component-a', { /* ... */ })
new Vue({ el: '#app' })
这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。
但是问题也就来了,如果项目的需求增多,随之而来会有更多的组件需要开发,就很难保证组件的名字不会冲突,这个时候就需要模块系统了,vue当然也想到了这一点,它提供了另外一种组件注册的方式——局部注册,来适应模块化的开发,局部注册的代码差不多是这样的:
var ComponentA = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA
}
})
引入模块系统后就可以变成下面这样:
ComponentA.js:
const ComponentA = { /* ... */ }
app.js:
const ComponentA = require(./ComponentA.js)
new Vue({
el: '#app',
components: {
'component-a': ComponentA
}
})
这样就解决了命名空间的问题,同时我们还可以做一个假设:Vue.component方法的作用就是声明一个组件并且把它放在实例的components对象中;
但是组件的注册又必须在实例化应用的前面,貌似这个假设行不通!
不着急,我们来看下Vue内置的组件transition,我们都知道这是一个全局组件,在任何实例或者组件内部都可以直接使用,那源码中应该有这个一个声明:
Vue.component('transition',{ /* ... */ })
很可惜我们没有找到,相应的,有这么一个组件的声明:
var Transition = {
name: 'transition',
props: transitionProps,
abstract: true,
.
.
.
}
说明它是局部注册的,那它又是怎么做到能全局使用呢?继续看下源码:
var platformComponents = {
Transition: Transition,
TransitionGroup: TransitionGroup
};
extend(Vue.options.components, platformComponents);
说明被挂在了Vue.options.components上面,那Vue.options.components又是怎么来的呢?
function initAssetRegisters(Vue) {
/**
* Create asset registration methods.
*/
ASSET_TYPES.forEach(function (type) {
Vue[type] = function (
id,
definition
) {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (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
}
};
});
}
Vue.options = Object.create(null);
ASSET_TYPES.forEach(function (type) {
Vue.options[type + 's'] = Object.create(null);
});
这里写的可能有点隐晦,花点时间找一下,从上面可知:Vue.componment注册的组件被挂在了Vue.options.components上,所以Vue.options.components上的组件就是全局组件;那这样的话Vue.options.components上面的组件和实例化的时候声明在components对象上的组件就是同一个意思了,在_init方法中我们找到了如下代码:
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
其中vm就是实例对象,$options就是我们传入的所有参数,类似于extend方法,把Vue.options上面的元素复制到实例中去,其中就包括components,所以我们可以得知,Vue.options.components上面的就是全局注册的组件,而实例上的components就是局部注册的组件,最后两者合在一起,就是所有注册的组件!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。