TypeScript中的装饰器和metadata reflection API反射:从新手到专家

文章目录

深入理解TypeScript的修饰器的实现,它让JavaScript实现反射和依赖注入成为可能。

教程分为四大部分

  • 第一部分:方法修饰器
  • 第一部分:属性修饰器&class修饰器
  • 第三部分:参数修饰器&修饰工厂
  • 第四部分:类型序列化&metadata反射API

在这篇文章中我们将会学习

  • 我们的JavaScript为什么需要反射
  • metadata反射API
  • 基础类型序列化
  • 复杂类型的序列化

为什么需要反射在JavaScript中?

反射常用于描述代码或者审核其它代码在同一个系统中

反射在组合,依赖注入,运行时类型断言,测试中非常有用

随着我们javascript应用越来越大,我们开始需要一些工具(像依赖反转控制,运行时类型断言)来管理应用增长的复杂性。现在的问题是JavaScript没有反射,这些工具或者特性将不能被实现,但是一些强大的编程语言实现的反射像 C#或者Java。

一个强大的反射API将允许我们测试未知的对象在运行的时候,找到所有关于它的信息。我们希望可以找到如下信息

  • 实体的名称
  • 实体的类型
  • 那个接口被实体实现
  • 实体的属性名称和类型
  • 实体的构造参数名称和类型

在JavaScript我们可以使用那个函数 Object.getOwnPropertyDescriptor() or Object.keys() 查找一些关于实体的信息,但我们需要反射实现更加强大的工具。

但是,这些情况将发生改变,因为TypeScript开始支持一些反射特性,让我们看看这些特性。

Metadata反射API

TypeScript团队开发人员使用Polyfilli垫片来为ES7添加反射API。TypeScript编译器现在可以为装饰器发出一些序列化的设计时元数据类型。

我们可以使用metadata反射API通过使用 reflect-metadata 包。

npm install reflect-metadata;

我们必须使用TypeScript1.5之后的版本和编译器标识emitDecoratorMetadata设置为true,我们也需要包含reflect-metadata.d.ts文件并载入Reflect.js。

我们接下来实现自己的修饰器并使用reflect metadata design key,但现在有三种类型的design key可用

  • 类型的metadatau使用metadata key"design:type"。
  • 参数的类型metadata使用metadata key "design:paramtypes"
  • 返回类型metadata使用metadata key "design:returntype"

让我们看几个示例。

获取属性的类型metadata使用reflect metadata API

让我们声明一个属性修饰器:

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

我们可以将它应用到class的一个属性中:


class Demo{
@logType // apply property decorator 应用属性修饰器
public attr1 : string;
}

上面的中console将会输出:

attr1 type: String

获取参数类型的metadata使用reflect metadata 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的一个方法上并获取参数类型信息。

    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
}
}

上面的示例中console将会输出:

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

获取返回类型的metadata使用reflect metadata API

我们也可以获取方法返回类型的信息使用"design:returntype" metadata key

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

基础类型的序列化

让我们再看看上面的design:paramtypes示例,注意接口IFoo和字面量对象{ test : string} 已序列化为对象,这是因为TypeScript只支持基础类型的序列化,下面是基础类型的序列化的规则。

  • 数值序列化为数值
  • string serialized as String
  • boolean serialized as Boolean
  • any serialized as Object
  • void serializes as undefined
  • Array serialized as Array
  • 如果是Tuple 序列化为Array
  • 如果是class 序列化为class的构造器
  • If an Enum serialized it as Number
  • 如果Enum 序列化Number
  • 如果它至少有一个调用签名,则序列化为Function
  • 否则序列化为对象Object ,包括接口interfaces

接口和字面量对象在未来也可以使用复杂的类型序列化,但是现在不可用。

复杂类型序列化

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;
}

我们还可以找到用于描述每种可能类型的类。 例如,我们可以找到提议用于对遗传接口进行序列化的类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添加一些很酷的功能。

结论

我们已经知道如何使用metadata reflection API。

全部为采集文章,文中的 联系方式 均不是 本人 的!

发表评论