深入理解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 asString
boolean
serialized asBoolean
any
serialized asObject
void
serializes asundefined
Array
serialized asArray
- 如果是
Tuple
序列化为Array
- 如果是
class
序列化为class的构造器 - If an
Enum
serialized it asNumber
- 如果
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。