视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001 知道1 知道21 知道41 知道61 知道81 知道101 知道121 知道141 知道161 知道181 知道201 知道221 知道241 知道261 知道281
问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501
Vue源码解读之Component组件注册的实现
2020-11-27 22:09:20 责编:小采
文档

什么是组件?

组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。

所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。

Vue可以有全局注册和局部注册两种方式来注册组件。

全局注册

注册方式

全局注册有以下两种注册方式:

通过Vue.component 直接注册。

Vue.component('button-counter', {
 //data选项必须是一个函数
 data: function () {
 return {
 count: 0
 }
 },
 template:'#clickBtn'
 })

通过Vue.extend来注册。

 var buttonComponent = Vue.extend({
 name:'button-counter',
 data: function () {
 return {
 count: 0
 }
 },
 template:'#clickBtn'
 });
 Vue.component('button-counter', buttonComponent);

具体过程

Vue初始化时,initGlobalAPI通过调用initAssetRegisters()进行组件注册。

function initAssetRegisters (Vue) {
 // 创建组件注册的方法
 // ASSET_TYPES在Vue内部定义,var ASSET_TYPES = ['component','directive','filter'];
 ASSET_TYPES.forEach(function (type) {
 Vue[type] = function (
 id,
 definition
 ) {
 //这里的definition指的是定义(Function或Object),是函数或者对象
 //如果definition不存在,直接返回options内type和id对应的
 //这里的options是指全局的组件,指令和过滤器,见图一
 if (!definition) {
 return this.options[type + 's'][id]
 } else {
 /* istanbul ignore if */
 if ("development" !== 'production' && type === 'component') {
 validateComponentName(id);
 }
 // 如果是component(组件)方法,并且definition是对象
 if (type === 'component' && isPlainObject(definition)) {
 definition.name = definition.name || id;
 //通过this.options._base.extend方法(也就是Vue.extend方法)将定义对象转化为构造器。
 //Vue.options._base = Vue;
 definition = this.options._base.extend(definition);
 }
 if (type === 'directive' && typeof definition === 'function') {
 definition = { bind: definition, update: definition };
 }
 // 将构造器赋值给 this.options[‘component'+ 's'][id]
 //全局的组件,指令和过滤器,统一挂在vue.options上。在init的时候利用mergeOptions合并策略侵入实例,供实例使用。
 this.options[type + 's'][id] = definition;
 return definition
 }
 };
 });
}

图一:

initAssetRegisters里面通过this.options._base.extend方法将定义对象转化为构造器,而options._base.extend其实就是Vue.extend。接下来我们就看一下Vue.extend做了什么。

Vue.extend = function (extendOptions) {
 extendOptions = extendOptions || {};
 var Super = this;
 var SuperId = Super.cid;
 //组件缓存
 var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
 //如果组件已经被缓存在extendOptions上则直接取出
 if (cachedCtors[SuperId]) {
 return cachedCtors[SuperId]
 }

 //如果有name属性,检验name拼写是否合法
 var name = extendOptions.name || Super.options.name;
 if ("development" !== 'production' && name) {
 validateComponentName(name);
 }

 var Sub = function VueComponent (options) {
 this._init(options);
 };
 //将vue上原型的方法挂在Sub.prototype中,Sub的实例同时也继承了vue.prototype上的所有属性和方法。
 //关于 prototype的学习:http://www.cnblogs.com/dolphinX/p/3286177.html
 Sub.prototype = Object.create(Super.prototype);
 //Sub构造函数修正,学习于https://www.cnblogs.com/SheilaSun/p/4397918.html
 Sub.prototype.constructor = Sub;
 Sub.cid = cid++;
 //通过vue的合并策略合并添加项到新的构造器上
 Sub.options = mergeOptions(
 Super.options,
 extendOptions
 );
 //缓存父构造器
 Sub['super'] = Super;

 // 处理props和computed响应式配置项
 if (Sub.options.props) {
 initProps$1(Sub);
 }
 if (Sub.options.computed) {
 initComputed$1(Sub);
 }

 // allow further extension/mixin/plugin usage
 Sub.extend = Super.extend;
 Sub.mixin = Super.mixin;
 Sub.use = Super.use;

 //在新的构造器上挂上vue的工具方法
 ASSET_TYPES.forEach(function (type) {
 Sub[type] = Super[type];
 });
 // enable recursive self-lookup
 if (name) {
 Sub.options.components[name] = Sub;
 }

 // keep a reference to the super options at extension time.
 // later at instantiation we can check if Super's options have
 // been updated.
 Sub.superOptions = Super.options;
 Sub.extendOptions = extendOptions;
 Sub.sealedOptions = extend({}, Sub.options);

 //缓存组件构造器在extendOptions上
 cachedCtors[SuperId] = Sub;
 return Sub
 };

vue.extend返回了一个带有附加Option的vue构造器。这个构造器被命名为Sub,等待render时候初始化。

initAssetRegisters完成之后,options下挂载了全局组件button-counter,如图:

接下来调用new Vue()渲染vue整体的生命周期

局部注册

如果不需要全局注册,或者是让组件使用在其它组件内,可以用选项对象的components属性实现局部注册。

注册方式

new Vue({
 el: '#components-demo',
 components:{
 'button-counter':{
 template:'#clickBtn',
 data: function () {
 return {
 count: 0
 }
 }
 }
 }
 })

具体过程

Vue局部组件注册也是通过initAssetRegisters()方法调用Vue.extend,不同的是在createComponent()时,initMixin()里面有判断

if (options && options._isComponent) {
 //因为Vue动态合并策略非常慢,并且内部组件的选项都不需要特殊处理。
 //调用initInternalComponent快捷方法,内部组件实例化。
 initInternalComponent(vm, options);
 }
 else {
 vm.$options = mergeOptions(
 resolveConstructorOptions(vm.constructor),
 options || {},
 vm
 );
 }
function initInternalComponent (vm, options) {
 var opts = vm.$options = Object.create(vm.constructor.options);
 // 这样做是因为它比动态枚举更快。
 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;
 }
}

opts的结构如图所示:

局部注册和全局注册不同的是,只有该类型的组件才可以访问局部注册的子组件,而全局注册是扩展到 Vue.options 下。在所有组件创建的过程中,都会从全局的 Vue.options.components 扩展到当前组件的 vm.$options.components 下,这就是全局注册的组件能被任意使用的原因。

组件名定义

定义组件名的方式有两种:

使用短横线形式

Vue.component('button-counter', {})

引用这个自定义元素时,必须用 <button-counter></button-counter>

使用驼峰的形式

Vue.component('buttonCounter', { })

此时在引用这个自定义元素时,两种命名方法都可以使用。也就是说,<buttonCounter><button-counter> 都是可行的。

注意,直接在 DOM (即非字符串的模板) 中使用时只有短横线是有效的。如下:

<div id="components-demo">
 <button-counter></button-counter>
</div>

可参考:https://www.gxlcms.com/article/144050.htm

下载本文
显示全文
专题