Vue源码探秘(十三)(组件注册)
2020/4/30 11:02:41
本文主要是介绍Vue源码探秘(十三)(组件注册),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
引言
在 Vue.js
中,除了它内置的组件如 keep-alive
、component
、transition
、transition-group
外,其它用户自定义的组件在使用前必须注册,否则会抛出如下错误:
组件的注册有两种方式:全局注册
和局部注册
。这里我们举两个例子:
// main.js import Vue from "vue"; import App from "App.vue"; Vue.component("app", App); new Vue({ el: "#app", template: "<app></app>" }); 复制代码
// App.vue <template> <div id="app"> <Hello/> </div> </template> <script > import Hello from 'Hello.vue' export default { name: 'app', components: { Hello } } </script> 复制代码
上面这两个例子中,第一个App
组件是全局注册,第二个Hello
组件是局部注册。
本小节,我会结合源码和上面举的两个例子分别分析这两种注册的实现。
全局注册
我们可以使用 Vue.compoment
来全局注册组件。所谓全局,就是注册出来的组件在任意一个创建出来的 Vue
实例中都可以使用。
Vue.component
是在哪里定义的呢,实际上是在 Vue
初始化时调用 initAssetRegisters
函数的时候。initAssetRegisters
定义在 src/core/global-api/assets.js
文件中:
// src/core/global-api/assets.js 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 之前也见过多次了:
export const ASSET_TYPES = ["component", "directive", "filter"]; 复制代码
现在我们只分析 ASSET_TYPES
为 component
的情况。这里的两个参数 id
和 definition
,分别对应例子中的 app
和 App 组件对象
。
函数首先会对组件名做校验检查组件名是否合法。
然后判断如果 definition
参数是对象则做进一步处理:如果 definition
对象没有 name
属性,则将参数 id
作为 definition.name
,然后调用 this.options._base.extend
(之前有介绍过,实际上就是 Vue.extend
)把 definition
转换成一个继承自 Vue
的构造函数。
转换完成后将这个构造函数赋值给 this.options.components[id]
,最后将其返回。
执行完上述操作后,对应我们的例子就变为了:
Vue.options = { components: { app: function VueComponent() {}, // 全局注册的 App 组件 keepAlive: {}, Transition: {}, TransitionGroup: {} }, filters: {}, directives: {}, _base: function Vue() {} }; 复制代码
也就是说全局注册会把组件扩展到 Vue.options
下。
执行完 Vue.component
,代码执行到 new Vue
这条语句。经过前面学习,我们知道创建一个 Vue
实例时,就会执行 _init
函数,而 _init
函数其中有一步合并 options
的操作。
合并 options
有两种情况,在执行外部 new Vue
时会走 else
逻辑:
vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); 复制代码
这里把 Vue.options
和外部编写的 options
一起合并到 vm.$options
上。
执行到这里,vm.$options
大概是这样的:
vm.$options = { el: "#app", template: "<app></app>", components: { __proto__: { // 继承自 Vue.options app: function VueComponent() {}, // 全局注册的 App 组件 keepAlive: {}, Transition: {}, TransitionGroup: {} } }, filters: {}, directives: {}, _base: function Vue() {} }; 复制代码
然后在创建 vnode
的过程中,会执行 _createElement
方法。回顾 _createElement
函数:
// src/core/vdom/create-element.js 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); if (config.isReservedTag(tag)) { // platform built-in elements 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 if
逻辑,这个逻辑针对的是组件,它通过调用了 resolveAsset
函数来判断。来看 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; } 复制代码
我们一起来梳理下resolveAsset
函数的查询逻辑:
检查 assets[id]
是否存在,存在返回,不存在往下走把 id
变成驼峰的形式再取,即检查assets[camelize(id)]
是否存在,存在返回,不存在继续往下走在驼峰的基础上把首字母再变成大写的形式再取,即检查 assets[capitalize(camelizedId)]
是否存在,存在返回,不存在抛出警告
这样我们就拿到了vm.$options.components[tag]
,即可以在 resolveAsset
的时候拿到这个组件的构造函数,并作为 createComponent
的钩子的参数。
局部注册
分析完全局注册,我们来看局部注册。
现在代码执行到创建 App
组件构造函数这里,回顾 Vue 源码探秘(九)(createComponent) , App
组件构造函数会通过 Vue.extend
来创建,其中有这么几行代码:
Sub.options = mergeOptions(Super.options, extendOptions); 复制代码
这里把 Vue.options
和我们在 App.vue
编写的 extendOptions
一起合并到 Sub.options
上 ,而 Sub
就是 App
组件的构造函数。
也就是说局部注册会把组件扩展到 Sub.options
下,另外也正是有这一步,Vue.options
会被合并到各个组件的构造函数 options
上,这也是为什么全局组件在任意一个地方都能使用。
之后就会调用 Sub
创建 App
组件实例,同样会执行 _init
函数,并且在合并 options
时会走 if
逻辑:
if (options && options._isComponent) { initInternalComponent(vm, options); } 复制代码
此时它会调用 initInternalComponent
函数:
export function initInternalComponent( vm: Component, options: InternalComponentOptions ) { const opts = (vm.$options = Object.create(vm.constructor.options)); // ... } 复制代码
initInternalComponent
函数执行了 vm.$options = Object.create(vm.constructor.options)
这一步,这里的 vm
对应我们例子就是 App 组件实例
,vm.$options
就继承了 Sub.options
。
之后同样是调用 resolveAsset
函数,拿到这个组件的构造函数,并作为createComponent
的钩子的参数。
这样 resolveAsset
函数在 if (hasOwn(assets, id))
逻辑判定就为 true
,直接就返回了 Hello
组件。之后的流程就不再赘述了。
总结
通过这一小节的分析,我们对两种组件的注册过程有了认识,并理解了全局注册
和局部注册
的差异:
全局注册的组件可以在任意地方使用,因为组件会通过 Vue.component
函数扩展到Vue.options
上,而各个组件初始化时都会将Vue.options
与自身options
合并,这样每个组件都能访问到这个全局注册的组件。局部注册的组件只能在当前组件使用,因为组件仅仅只是扩展到 Sub.options
也就是当前组件构造函数的options
上。
这篇关于Vue源码探秘(十三)(组件注册)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04package.json 文件位置在哪?-icode9专业技术文章分享
- 2024-10-01Craco.js学习:从入门到实践指南
- 2024-10-01Create-React-App学习:入门与实践指南
- 2024-10-01CSS-in-JS学习:从入门到实践指南
- 2024-09-30JSX语法学习:从入门到初步掌握
- 2024-09-30Mock.js学习:入门教程与实战演练
- 2024-09-30React Hooks学习:从入门到实践
- 2024-09-30受控组件学习:React中的基础入门教程
- 2024-09-29JS定时器教程:初学者必看指南
- 2024-09-29JS对象教程:初学者的全面指南