vue-class-component v8浅析

905 阅读3分钟

在使用vue-cli创建项目时,选择vue3.X和Typescript后会发现,项目搭配了vue-class-component使用,且版本为^8.0.0-0。然而官方v8文档没有出,接下来对这个库做个入门分析,看看它是如何处理类组件的。

为什么用vue-class-component?

vue2 对 TS 的支持并不友好,原因在于vue2的Option API风格。options是个简单对象,而ts是一种类型系统、面向对象的语法。两者有点不匹配。所以 vue3 要跟 TS 的整合,通常使用 vue-class-component , 用基于 class(类) 的组件书写方式。

当然不使用vue-class-component也是可以的,只是当项目开发到一定复杂度之后,太多的文件和逻辑不容易维护和阅读,不如类面向对象的特点,逻辑清晰、模块化,且有继承等高级特性。

vue-class-component是什么?

vue-class-component 是 vue 的官方库,作用是用类的方式编写组件。通过以类样式定义组件,不仅可以更改语法,而且还可以利用一些 ECMAScript 语言特性,如类继承和装饰器。

装饰器是什么?

装饰器(Decorator)用来增强 JavaScript 类(class)的功能,许多面向对象的语言都有这种语法,目前有一个提案将其引入了 ECMAScript。

装饰器是一种函数,写成@ + 函数名。它可以放在类和类方法的定义前面。

这里仅介绍下类装饰器

//装饰器是一个函数,API 的类型描述如下。
//装饰器函数调用时,会接收到两个参数。
-   `value`:被装饰的值,某些情况下可能是`undefined`(装饰属性时)
-   `context`:一个对象,包含了被装饰的值的上下文信息
@decorator
class A {}

// 等同于
class A {}
A = decorator(A) || A;
//不含参数
@testable
class MyTestableClass {
  // ...
}

function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true
//含参数
function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。

vue-class-component的处理逻辑?

image.png

image.png

我们看到,VueImpl立即执行函数目的是在其构造函数中注入registerHooks with __vccOpts三个属性,__vccOpts属性在Vue中用来判断是不是类组件。

image.png

image.png

再看看Options装饰器 image.png

接着执行到export default XXX处,会在Vue-loader中获取__vccOpts属性,从而触发__vccOpts的get方法,生成options对象,这个对象就等价于vue2中的配置项了。

get: function get() {
  if (this === Vue) {
    return {};
  }
  //当前类组件
  var Ctor = this;
  //查找有没有__c属性
  var cache = getOwn(Ctor, '__c');

  if (cache) {
    return cache;
  } // If the options are provided via decorator use it as a base

  //可以简单理解为对象复制
  var options = _objectSpread2({}, getOwn(Ctor, '__o'));

  Ctor.__c = options; // 增加options属性

  //查找原型链继承的 VueImpl
  var Super = getSuper(Ctor);

  if (Super) {
    options["extends"] = Super.__vccOpts;
  } // Inject base options as a mixin

  //查找__b属性
  var base = getOwn(Ctor, '__b');

  if (base) {
    options.mixins = options.mixins || [];
    options.mixins.unshift(base);
  }

  options.methods = _objectSpread2({}, options.methods);
  options.computed = _objectSpread2({}, options.computed);
  var proto = Ctor.prototype;
  //遍历原型上的属性
  Object.getOwnPropertyNames(proto).forEach(function (key) {
    if (key === 'constructor') {
      return;
    } // hooks

    //查找原型上的属性有没有在__h内,如果有赋值给options对应属性上
    if (Ctor.__h.indexOf(key) > -1) {
      options[key] = proto[key];
      return;
    }
    //获取属性的属性描述符
    var descriptor = Object.getOwnPropertyDescriptor(proto, key); // methods

    if (typeof descriptor.value === 'function') {
      options.methods[key] = descriptor.value;
      return;
    } // computed properties


    if (descriptor.get || descriptor.set) {
      options.computed[key] = {
        get: descriptor.get,
        set: descriptor.set
      };
      return;
    }
  });

  options.setup = function (props, ctx) {
    /**省略**/
  };

  var decorators = getOwn(Ctor, '__d');

  if (decorators) {
    decorators.forEach(function (fn) {
      return fn(options);
    });
  } // from Vue Loader


  var injections = ['render', 'ssrRender', '__file', '__cssModules', '__scopeId', '__hmrId'];
  injections.forEach(function (key) {
    if (Ctor[key]) {
      options[key] = Ctor[key];
    }
  });
  return options;
}

后话:接着会执行createApp(App), app.mount('#app'),在mount方法中会调用setup方法,接着执行组件更新、组件渲染,最终将真实dom挂载到页面上。