Code前端首页关于Code前端联系我们

深入介绍不同方式,从选项到Vue组件

terry 2年前 (2023-09-08) 阅读数 161 #Vue

问题背景

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

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

于是我按照代码流程查看错误原因。找到报告错误的代码:

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

Comp 是一个在远程加载的单文件 Vue 组件之后编译并发布到 CDN 的组件。我们知道,Vue 文件的组件经过 vue-loader 处理后,一般都会变成这种数据结构的对象:

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

总体结构和我们平时写的组件选项类似。该对象在Vue中被处理为全局h函数后即可显示。不过这次查看从远程环境加载的对象后,会显示为:

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

如果你熟悉Vue,或许一眼就能看出问题所在。不过由于我平时比较关注Vue的响应能力以及它的各种接口API,所以我对Vue的整体理解有些欠缺。

问题原因:外部组件被别人更新了。更新后,组件不会写入默认导出选项;但默认导出 Vue.extend(options);

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

那为什么Vue.extend后外部加载后会报初始化错误,而开发者通过组件渲染自己测试却是正常的呢?

Vue 组件

要详细了解上述具体原因,需要对Vue组件有深入的了解。当我们谈论Vue组件化时,我通常会想到Vue.component,但很少使用Vue.extend。

Vue.extend原理分析

当我第一次看到扩展方法时,我其实觉得很奇怪。后来我正确理解后,觉得这个名字很贴切。

Java中的extends是一个关键字,意思是继承。这里的Vue.extend也有继承的意思。 extend 基于 Vue 构造函数创建一个新的构造函数

这里可以参考Vue在2.0.0版本中对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版本的实现源码(去掉了一些不相关组件功能中的分支),方便和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后可以获得组件的实例对象。

最重要的是弄清楚你平时写的选项和组件实例对象之间的关系。具体换算关系我们参考下图:

stateDiagram-v2

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

PS。以上是我个人对Vue组件的理解。有什么不合适的地方请告诉我~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门