首页 > 编程 > JavaScript > 正文

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

2019-11-19 13:10:07
字体:
来源:转载
供稿:网友

什么是组件?

组件 (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.VeVB.COm/article/144050.htm

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

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表