TypeScript中的装饰器&元数据反射:从新手到专家四

TypeScript中的装饰器&元数据反射:从新手到专家四

本文译自:Decorators & metadata reflection in TypeScript: From Novice to Expert (Part IV)

深入了解装饰器的TypeScript实现以及它们如何使反射或依赖注入等新的令人兴奋的JavaScript功能成为可能。

本文是系列文章的第三部分:

我将假设您已经阅读了这个系列之前所有的文章。

在本系列的上一篇文章中,我们了解了什么是装饰器以及它们是如何在TypeScript中实现的。我们知道如何使用类,方法,属性和参数装饰器,如何创建装饰器工厂以及如何使用装饰器工厂来实现可配置的装饰器。

在这篇文章中,我们将了解:

  • 1.为什么我们需要在JavaScript中进行反射?
  • 2.元数据反射API
  • 3.基本类型序列化
  • 4.复杂类型序列化

让我们首先了解为什么我们需要在JavaScript中进行反射。

1.为什么我们需要在JavaScript中进行反射?

名称反射用于描述能够检查同一系统(或其自身)中的其他代码的代码。

反射对于许多用例(组合/依赖注入,运行时类型断言,测试)很有用。

我们的JavaScript应用程序越来越大,我们开始需要一些工具(如控件容器的反转)和像(运行时类型断言)这样的功能来管理这种日益增加的复杂性。问题在于,由于JavaScript中没有反射,因此无法实现某些工具和功能,或者至少它们不能像C#或Java这样的编程语言那样强大。

强大的反射API应该允许我们在运行时检查未知对象并找出有关它的所有内容。我们应该能够找到像这样的东西:

  • 实体的名称。
  • 实体的类型。
  • 哪些接口由实体实现。
  • 实体属性的名称和类型。
  • 实体的构造函数参数的名称和类型。

在JavaScript中,我们可以使用Object.getOwnPropertyDescriptor()Object.keys()等函数来查找有关实体的一些信息,但我们需要反思来实现更强大的开发工具。

但是,事情即将发生变化,因为TypeScript开始支持一些Reflection功能。我们来看看这个功能:

2.元数据反射API

对元数据反射API的本机JavaScript支持处于开发的早期阶段。有一项建议将装饰器添加到ES7,以及用于装饰器元数据的ES7 Reflection API的原型,该原型可在线获得。

来自TypeScript团队的一些人一直在使用Polyfill来获取ES7 Reflection API的原型,而TypeScript编译器现在能够为装饰器发出一些序列化的设计时类型元数据

我们可以使用reflect-metadata包来使用此元数据反射API polyfill :

npm install reflect-metadata;

我们必须将它与TypeScript 1.5一起使用,并将编译器标志emitDecoratorMetadata设置为true。我们还需要包含一个引用reflect-metadata.d.ts。并加载Reflect.js文件。

然后,我们需要实现自己的装饰器并使用一个可用的反射元数据设计键。目前只有三种可用:

  • 类型元数据使用元数据键"design:type"
  • 参数类型元数据使用元数据键"design:paramtypes"
  • 返回类型元数据使用元数据键"design:returntype"

我们来看几个例子。

A)使用反射元数据API获取类型元数据

让我们声明以下属性装饰器:

function logType(target : any, key : string) {
  var t = Reflect.getMetadata("design:type", target, key);
  console.log(`${key} type: ${t.name}`);
}

然后我们可以将它应用于类的一个属性以获取其类型:

class Demo{ 
  @logType // apply property decorator
  public attr1 : string;
}

上面的示例在控制台中记录以下内容:

attr1 type: String

B)使用反射元数据API获取参数类型元数据

让我们声明以下参数装饰器:

function logParamTypes(target : any, key : string) {
  var types = Reflect.getMetadata("design:paramtypes", target, key);
  var s = types.map(a => a.name).join();
  console.log(`${key} param types: ${s}`);
}  

然后,我们可以将它应用于类的一个方法,以获取有关其参数类型的信息:

class Foo {}
interface IFoo {}

class Demo {
  @logParameters // apply parameter decorator
  doSomething(
    param1: string,
    param2: number,
    param3: Foo,
    param4: { test: string },
    param5: IFoo,
    param6: Function,
    param7: (a: number) => void
  ): number {
    return 1;
  }
}

上面的示例在控制台中记录以下内容:

doSomething param types: String, Number, Foo, Object, Object, Function, Function

C)使用反射元数据API获取返回类型元数据

我们还可以使用"design:returntype"元数据键获取有关方法返回类型的信息:

Reflect.getMetadata("design:returntype", target, key);

3.基本类型序列化

让我们再看看"design:paramtypes"上面的例子。请注意,接口IFoo和对象文字{ test : string}被序列化为Object。这是因为TypeScript仅支持基本类型序列化。基本类型序列化规则是:

  • number 序列化为 Number
  • string 序列化为 String
  • boolean 序列化为 Boolean
  • any 序列化为 Object
  • void 序列化为 undefined
  • Array 序列化为 Array
  • 如果是Tuple,序列化为Array
  • 如果class将它序列化为类构造函数
  • 如果Enum序列化为Number
  • 如果至少有一个呼叫签名,则序列化为 Function
  • 否则序列化为Object(包括接口)

接口和对象文字可能在将来通过复杂类型序列化进行序列化,但此功能目前不可用。

4.复杂类型序列化

TypeScript团队正在制定一项提案,允许我们为复杂类型生成元数据。

他们的提案描述了如何序列化一些复杂类型。上面的序列化规则仍将用于基本类型,但不同的序列化逻辑将用于复杂类型。在提案中有一个基本类型,用于描述所有可能的类型:

/** 
  * Basic shape for a type.
  */
interface _Type {
  /** 
    * Describes the specific shape of the type.
    * @remarks 
    * One of: "typeparameter", "typereference", "interface", "tuple", "union", 
    * or "function".
    */
  kind: string; 
}

我们还可以找到将用于描述每种可能类型的类。例如,我们可以找到建议用于序列化遗传接口的类interface foo<bar> { /* ... */}

/**
  * Describes a generic interface.
  */
interface InterfaceType extends _Type {
  kind: string; // "interface"

  /**
    * Generic type parameters for the type. May be undefined.
    */
  typeParameters?: TypeParameter[];

  /**
    * Implemented interfaces.
    */
  implements?: Type[];

  /**
    * Members for the type. May be undefined. 
    * @remarks Contains property, accessor, and method declarations.
    */
  members?: { [key: string | symbol | number]: Type; };

  /**
    * Call signatures for the type. May be undefined.
    */
  call?: Signature[];

  /**
    * Construct signatures for the type. May be undefined.
    */
  construct?: Signature[];

  /**
    * Index signatures for the type. May be undefined.
    */
  index?: Signature[];
}

如上所示,将有一个属性指示已实现的接口:

/**
 * Implemented interfaces.
 */
implements?: Type[];

该信息可用于执行诸如验证实体是否在运行时实现某个接口,这对IoC容器非常有用。

我们不知道何时将复杂类型序列化支持添加到TypeScript中,但我们不能等待,因为我们计划使用它为我们用于JavaScript的强大IoC容器添加一些很酷的功能:InversifyJS

5.结论

在本系列中,我们深入了解了4种可用的装饰器类型、如何创建装饰器工厂以及如何使用装饰器工厂来实现可配置的装饰器。

我们还知道如何使用元数据反射API。

我们将在未来更新此博客并撰写有关元数据反射API的更多信息。如果您不想错过,请不要忘记订阅

请随时通过@OweR_ReLoaDeD@WolkSoftwareLtd与我们讨论这篇文章。

编辑于 2018-08-17 11:13