组件注册时做了什么?
在Vue
中使用组件,要做的第一步就是注册。Vue
提供了全局注册和局部注册两种方式。
全局注册方式如下:
Vue.component('my-component-name', { /* ... */ })
局部注册方式如下:
var ComponentA = { /* ... */ } new Vue({ el: '#app', components: { 'component-a': ComponentA } })
全局注册的组件,会在任何Vue
实例中使用。局部注册的组件,只能在该组件的注册地,也就是注册该组件的Vue
实例中使用,甚至Vue
实例的子组件中也不能使用。
有一定Vue
使用经验的小伙伴都了解上面的差异,但是为啥会有这样的差异呢?我们从组件注册的代码实现上进行解释。
// Vue.component的核心代码 // ASSET_TYPES = ['component', 'directive', 'filter'] ASSET_TYPES.forEach(type => { Vue[type] = function (id, definition ){ if (!definition) { return this.options[type + 's'][id] } else { // 组件注册 if (type === 'component' && isPlainObject(definition)) { definition.name = definition.name || id // 如果definition是一个对象,需要调用Vue.extend()转换成函数。Vue.extend会创建一个Vue的子类(组件类),并返回子类的构造函数。 definition = this.options._base.extend(definition) } // ...省略其他代码 // 这里很关键,将组件添加到构造函数的选项对象中Vue.options上。 this.options[type + 's'][id] = definition return definition } } })
// Vue的构造函数 function Vue(options){ if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) } // Vue的初始化中进行选项对象的合并 Vue.prototype._init = function (options) { const vm = this vm._uid = uid++ vm._isVue = true // ...省略其他代码 if (options && options._isComponent) { initInternalComponent(vm, options) } else { // 合并vue选项对象,合并构造函数的选项对象和实例中的选项对象 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } // ...省略其他代码 }
以上摘取了组件注册的主要代码。可以看到Vue
实例的选项对象由Vue
的构造函数选项对象和Vue
实例的选项对象两部分组成。
全局注册的组件,实际上通过Vue.component
添加到了Vue
构造函数的选项对象 Vue.options.components
上了。
Vue
在实例化时(new Vue(options)
)所指定的选项对象会与构造函数的选项对象合并作为Vue
实例最终的选项对象。因此,全局注册的组件在所有的Vue
实例中都可以使用,而在Vue
实例中局部注册的组件只会影响Vue
实例本身。
为啥在HTML模板中可以正常使用组件标签?
我们知道组件可以跟普通的HTML
一样在模板中直接使用。例如:
<div id="app"> <!--使用组件button-counter--> <button-counter></button-counter> </div>
// 全局注册一个名为 button-counter 的组件 Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>' }) // 创建Vue实例 new Vue({ el: '#app' })
那么,当Vue
解析到自定义的组件标签时是如何处理的呢?
Vue
对组件标签的解析与普通HTML
标签的解析一样,不会因为是非 HTML
标准的标签而特殊处理。处理过程中第一个不同的地方出现在vnode
节点创建时。vue
内部通过_createElement
函数实现vnode
的创建。
export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array<VNode> { //...省略其他代码 let vnode, ns if (typeof tag === 'string') { let Ctor ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) // 如果是普通的HTML标签 if (config.isReservedTag(tag)) { vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // 如果是组件标签,e.g. my-custom-tag vnode = createComponent(Ctor, data, context, children, tag) } else { vnode = new VNode( tag, data, children, undefined, undefined, context ) } } else { // direct component options / constructor vnode = createComponent(tag, data, context, children) } if (Array.isArray(vnode)) { return vnode } else if (isDef(vnode)) { if (isDef(ns)) applyNS(vnode, ns) if (isDef(data)) registerDeepBindings(data) return vnode } else { return createEmptyVNode() } }