深入介绍从options到Vue组件的几种方式

问题背景

最近发现通过远程加载组件后,在组件初始化的过程中,浏览器控制台有报错:

深入介绍从options到Vue组件的几种方式

于是就跟着代码流程看看报错的原因。定位到报错处的代码:

const originalVal = comp.props[key].default || '';
 

其中comp是远程加载的Vue单文件组件经过编译之后的发布到CDN上的组件。我们知道一般Vue单文件组件经过vue-loader处理之后一般会变成具有这种数据结构的对象:

深入介绍从options到Vue组件的几种方式

整体结构和我们通常的写的组件options比较像似,这个对象作为Vue中比较常见的h函数处理后就能渲染出来了。然而,经过查看本次从远程环境中加载的对象,显示为:

深入介绍从options到Vue组件的几种方式

如果对Vue比较熟悉的话,可能一眼就能看出问题所在了。但是由于平时都比较关注Vue的响应式原理、各种接口API了,反倒对Vue整体上的了解有些欠缺了。

问题的根源:远程组件被别人更新了,更新之后组件的编写方式不在是export default options; 而是export default Vue.extend(options);

深入介绍从options到Vue组件的几种方式

那为什么经过Vue.extend之后远程加载后进行初始化报错了,但是开发同学通过组件渲染自测是正常的呢?

Vue组件

要详细理解上述的具体原因,需要从Vue组件进行深入理解。说起Vue组件化,通常都会想起Vue.component, 而对于Vue.extend的使用反而比较少。

Vue.extend原理分析

刚开始见到extend方法时,其实觉得比较奇怪的,后来仔细理解了下,觉得名字挺贴切的。

在Java中extends是关键字,表示继承的含义,在这里Vue.extend也表示继承的含义,extend在Vue这个构造函数的基础上创建了一个新的构造函数

这里可以参考Version2.0.0版本中,Vue对extend方法的实现。其实,整个extend的流程是比较清晰的(可以参考代码中给出的一点点注释)。

主要是通过Object.create方法实现原型链继承(对于Object.create的用法可以参考之前的一篇文章:链接),返回的Sub是一个构造函数,通过new方式调用后,就会通过调用Vue原型链上的_init方法进行初始化,后续流程和new Vue(options)创建一个Vue实例基本上就一致了。

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;
    // 1. 定义extend返回对象
    var Sub = function VueComponent (options) {
      this._init(options);
    };
    // 1.1 通过Object.create方法进行原型链继承
    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;
    Sub.cid = cid++;
    // 2. 设置构造函数上的options, 用来存储配置
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    );
    Sub['super'] = Super;
    Sub.extend = Super.extend;
    config._assetTypes.forEach(function (type) {
      Sub[type] = Super[type];
    });
    if (name) {
      Sub.options.components[name] = Sub;
    }
    Sub.superOptions = Super.options;
    Sub.extendOptions = extendOptions;
    if (isFirstExtend) {
      extendOptions._Ctor = Sub;
    }
    // 3. 返回新的构造函数
    return Sub
};
 

Vue.component原理分析

在进行组件注册时,Vue.component是一个使用频率比较高的方法,一般大家对调用方法都比较熟悉了。简单介绍一下Vue 2.0.0版本的实现源码(删除了一些无关component函数里面的分支),便于与Vue.extend进行比较

Vue[type] = function (
  id,
  definition
) {
  if (!definition) {
    // 直接获取组件
    return this.options[type + 's'][id]
  } else {
    if (type === 'component' && isPlainObject(definition)) {
      definition.name = definition.name || id;
      // 定义组件的构造函数
      definition = Vue.extend(definition);
    }
    this.options[type + 's'][id] = definition;
    return definition
  }
};
 

从Vue中的代码实现中可以获取以下几点信息:

  1. Vue.component函数是在Vue.extend的基础上进行了二次的封住,在获取组件的构造函数(Vue.extend返回一个构造函数)之后,将构造函数放置在Vue的options属性上
  2. 通过Vue.component函数可以直接获得相应的组件构造函数
  3. 通过Vue.component函数可以判断组件是否存在

为什么更改成Vue.extend(options),测试组件渲染还是正常呢?

这是因为Vue在使用h函数(createElement方法)时,对传入的参数进行了判断,然后进行分类处理,其实最终的结果还是转换成组件的构造函数。然而我们自己的代码逻辑中并未对构造函数这种形式的组件进行判断和处理,导致异常的发生。

一般来说有两种解决方法:

  • 规范内部对组件的写法,统一成一种格式,这样开发同学在写代码时会比较清晰
  • 借鉴Vue的做法,对两种组件写法进行兼容处理

总结

Vue.extend方法创建一个组件的构造函数,通过new调用后可以获得组件的一个实例对象。

主要是要捋清楚平时所写的options与组件实例对象之间的关系。具体转换关系可以参考下图:

stateDiagram-v2

Options --> Vue
Options --> Vue.extend
Options --> Vue.component
Vue.component-->Vue.extend
Vue --> instance
Vue.extend --> instance

PS. 以上就是个人对Vue组件的一些理解,有不当之处敬请不吝赐教~

原创文章,作者:我心飞翔,如若转载,请注明出处:https://www.pipipi.net/14727.html

发表评论

登录后才能评论