[Vue源码剖析]如何实现组件

官网上关于组件继承分为两大类,全局组件和局部组件。无论哪种方式,最核心的是创建组件,然后根据场景不同注册组件。

有一点要牢记,“Vue.js 组件其实都是被扩展的 Vue 实例”

1. 全局组件

// 方式一
var MyComponent = Vue.extend({
    name: 'my-component',
    template: '<div>A custom component!</div>'
});
Vue.component('my-component', MyComponent);

// 方式二
Vue.component('my-component', {
    name: 'my-component',
    template: '<div>A custom component!</div>'
});

// 使用组件
<div id="example">
    <my-component></my-component>
</div>

主要涉及到两个静态方法:

  • Vue.extend:通过扩展Vue实例的方法创建组件
  • Vue.component:注册组件

先来看看Vue.extend源码,解释参考中文注释:

Vue.extend = function (extendOptions) {
  extendOptions = extendOptions || {};
  var Super = this;
  var isFirstExtend = Super.cid === 0;
  if (isFirstExtend && extendOptions._Ctor) {
    return extendOptions._Ctor;
  }
  var name = extendOptions.name || Super.options.name;
  // 如果有name属性,即组件名称,检测name拼写是否合法
  if ('development' !== 'production') {
    if (!/^[a-zA-Z][\w-]*$/.test(name)) {
      warn('Invalid component name: "' + name + '". Component names ' + 'can only contain alphanumeric characaters and the hyphen.');
      name = null;
    }
  }
  // 创建一个VueComponent 构造函数,函数名为‘VueComponent’或者name
  var Sub = createClass(name || 'VueComponent');
  // 构造函数原型继承Vue.prototype
  Sub.prototype = Object.create(Super.prototype);
  Sub.prototype.constructor = Sub;
  Sub.cid = cid++;
  // 合并Vue.options和extendOptions,作为新构造函数的静态属性options   
  Sub.options = mergeOptions(Super.options, extendOptions);
  //'super'静态属性指向Vue函数
  Sub['super'] = Super;
  // start-----------------拷贝Vue静态方法   
  // allow further extension
  Sub.extend = Super.extend;
  // create asset registers, so extended classes
  // can have their private assets too.
  config._assetTypes.forEach(function (type) {
    Sub[type] = Super[type];
  });
  // end-----------------拷贝Vue静态方法   
  // enable recursive self-lookup
  if (name) {
    Sub.options.components[name] = Sub;
  }
  // cache constructor:缓存该构造函数
  if (isFirstExtend) {
    extendOptions._Ctor = Sub;
  }
  return Sub;
};

可以看到,Vue.extend的关键点在于:创建一个构造函数function VueComponent(options) { this._init(options) },通过原型链继承Vue原型上的属性和方法,再讲Vue的静态函数赋值给该构造函数。

再看看Vue.component源码,解释参考中文注释:

// _assetTypes: ['component', 'directive', 'elementDirective', 'filter', 'transition', 'partial']
config._assetTypes.forEach(function (type) {
  // 静态方法Vue.component
  Vue[type] = function (id, definition) {
    if (!definition) {
      return this.options[type + 's'][id];
    } else {
      /* istanbul ignore if */
      if ('development' !== 'production') {
        if (type === 'component' && (commonTagRE.test(id) || reservedTagRE.test(id))) {
          warn('Do not use built-in or reserved HTML elements as component ' + 'id: ' + id);
        }
      }
     // 如果第二个参数是简单对象,则需要通过Vue.extend创建组件构造函数
      if (type === 'component' && isPlainObject(definition)) {
        if (!definition.name) {
          definition.name = id;
        }
        definition = Vue.extend(definition);
      }
     // 将组件函数加入Vue静态属性options.components中,也就是,全局注入该组件
      this.options[type + 's'][id] = definition;
      return definition;
    }
  };
});

方法Vue.component的关键点是,将组件函数注入到Vue静态属性中,这样可以根据组件名称找到对应的构造函数,从而创建组件实例。

2. 局部组件

var MyComponent = Vue.extend({
    template: '<div>A custom component!</div>'
});

new Vue({
    el: '#example',
    components: {
        'my-component': MyComponent,
        'other-component': {
            template: '<div>A custom component!</div>'
        }
    }
});

注册局部组件的特点就是在创建Vue实例的时候,定义components属性,该属性是一个简单对象,key值为组件名称,value可以是具体的组件函数,或者创建组件必须的options对象。

来看看Vue如何解析components属性,解释参考中文注释:

Vue.prototype._init = function (options) {
    options = options || {};
    ....
    // merge options.
    options = this.$options = mergeOptions(this.constructor.options, options, this);
    ...
};

function mergeOptions(parent, child, vm) {
    //解析components属性
    guardComponents(child);
    guardProps(child);
    ...
}

function guardComponents(options) {
    if (options.components) {
        // 将对象转为数组
        var components = options.components = guardArrayAssets(options.components);
        //ids数组包含组件名
        var ids = Object.keys(components);
        var def;
        if ('development' !== 'production') {
            var map = options._componentNameMap = {};
        }
        // 遍历组件数组
        for (var i = 0, l = ids.length; i < l; i++) {
            var key = ids[i];
            if (commonTagRE.test(key) || reservedTagRE.test(key)) {
                'development' !== 'production' && warn('Do not use built-in or reserved HTML elements as component ' + 'id: ' + key);
                continue;
            }
            // record a all lowercase <-> kebab-case mapping for
            // possible custom element case error warning
            if ('development' !== 'production') {
                map[key.replace(/-/g, '').toLowerCase()] = hyphenate(key);
            }
            def = components[key];
            // 如果是组件定义是简单对象-对象字面量,那么需要根据该对象创建组件函数
            if (isPlainObject(def)) {
                components[key] = Vue.extend(def);
            }
        }
    }
}

在创建Vue实例过程中,经过guardComponents()函数处理之后,能够保证该Vue实例中的components属性,都是由{组件名:组件函数}构成的,这样在后续使用时,可以直接利用实例内部的组件构建函数创建组件实例。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容