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


Posted in Javascript onAugust 24, 2018

什么是组件?

组件 (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>

可参考:https://3water.com/article/144050.htm

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

Javascript 相关文章推荐
Jquery 滑入滑出效果实现代码
Mar 27 Javascript
jquery中通过父级查找进行定位示例
Jun 28 Javascript
javascript快速排序算法详解
Sep 17 Javascript
jquery制做精致的倒计时特效
Jun 13 Javascript
jQuery回到顶部的代码
Jul 09 Javascript
JS实现的表头列头固定页面功能示例
Jan 10 Javascript
JS简单封装的图片无缝滚动效果示例【测试可用】
Mar 22 Javascript
原生JS实现自定义滚动条效果
Oct 27 Javascript
vue单页缓存存在的问题及解决方案(小结)
Sep 25 Javascript
vue项目中使用Hbuilder打包app 设置沉浸式状态栏的方法
Oct 22 Javascript
使用webpack4编译并压缩ES6代码的方法示例
Apr 24 Javascript
vue route新窗口跳转页面并且携带与接收参数
Apr 10 Vue.js
element-ui 关于获取select 的label值方法
Aug 24 #Javascript
微信小程序用户信息encryptedData详解
Aug 24 #Javascript
element-ui 中的table的列隐藏问题解决
Aug 24 #Javascript
实例详解ztree在vue项目中使用并且带有搜索功能
Aug 24 #Javascript
基于Vue 2.0 监听文本框内容变化及ref的使用说明介绍
Aug 24 #Javascript
element-ui 表格数据时间格式化的方法
Aug 24 #Javascript
vue select选择框数据变化监听方法
Aug 24 #Javascript
You might like
php下使用以下代码连接并测试
2008/04/09 PHP
php中处理mysql_fetch_assoc返回来的数组 不用foreach----echo
2011/05/04 PHP
php列出一个目录下的所有文件的代码
2012/10/09 PHP
ThinkPHP添加更新标签的方法
2014/12/05 PHP
CI(CodeIgniter)框架视图中加载视图的方法
2017/03/24 PHP
PHP常用函数之base64图片上传功能详解
2019/10/21 PHP
javascript 模拟JQuery的Ready方法实现并出现的问题
2009/12/06 Javascript
三种动态加载js的jquery实例代码另附去除js方法
2014/04/30 Javascript
js清空form表单中的内容示例
2014/05/20 Javascript
JavaScript获取鼠标移动时的坐标(兼容IE8、chome谷歌、Firefox)
2014/09/13 Javascript
基于javascript实现九宫格大转盘效果
2020/05/28 Javascript
JS模仿腾讯图片站的图片翻页按钮效果完整实例
2016/06/21 Javascript
浅谈toLowerCase和toLocaleLowerCase的区别
2016/08/15 Javascript
javascript对浅拷贝和深拷贝的详解
2016/10/14 Javascript
Express与NodeJs创建服务器的两种方法
2017/02/06 NodeJs
EsLint入门学习教程
2017/02/17 Javascript
关于Node.js的events.EventEmitter用法介绍
2017/04/01 Javascript
Vue中添加手机验证码组件功能操作方法
2017/12/07 Javascript
JavaScript封装的常用工具类库bee.js用法详解【经典类库】
2018/09/03 Javascript
Vue-axios-post数据后端接不到问题解决
2020/01/09 Javascript
小程序中设置缓存过期的实现方法
2020/01/14 Javascript
带你使用webpack快速构建web项目的方法
2020/11/12 Javascript
[01:21]2018DOTA2亚洲邀请赛4.5采访 打DOTA2也能有女朋友?
2018/04/06 DOTA
[01:04:35]2018DOTA2亚洲邀请赛 4.3 突围赛 Secret vs VG 第一场
2018/04/04 DOTA
Python 字符串操作实现代码(截取/替换/查找/分割)
2013/06/08 Python
Python json模块使用实例
2015/04/11 Python
Using Django with GAE Python 后台抓取多个网站的页面全文
2016/02/17 Python
Python pymongo模块常用操作分析
2018/09/01 Python
Python3+PyInstall+Sciter解决报错缺少dll、html等文件问题
2019/07/15 Python
浅谈python 中的 type(), dtype(), astype()的区别
2020/04/09 Python
Python+PyQt5实现灭霸响指功能
2020/05/25 Python
雅诗兰黛旗下专业男士保养领导品牌:Lab Series
2017/05/15 全球购物
Perfume’s Club德国官网:在线购买香水
2019/04/08 全球购物
护士个人总结范文
2015/02/13 职场文书
采购内勤岗位职责
2015/04/13 职场文书
html+css实现滚动到元素位置显示加载动画效果
2021/08/02 HTML / CSS