欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue源码解读之Component组件注册的实现

程序员文章站 2022-07-22 09:23:53
什么是组件? 组件 (component) 是 vue.js 最强大的功能之一。组件可以扩展 html 元素,封装可重用的代码。在较高层面上,组件是自定义元素,vue.j...

什么是组件?

组件 (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
   }
  };
 });
}

图一:

Vue源码解读之Component组件注册的实现

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,如图:

Vue源码解读之Component组件注册的实现

接下来调用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源码解读之Component组件注册的实现

局部注册和全局注册不同的是,只有该类型的组件才可以访问局部注册的子组件,而全局注册是扩展到 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>

可参考:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。