当前位置:  首页>> 技术小册>> TypeScript入门指南

TypeScript 增加了很多有用的特性,比如类型注解、接口、泛型、命名空间等,使得它在编写大型项目时更加可靠和易于维护。其中 TypeScript 装饰器与反射元数据这两个特性是非常强大的,它们能够让我们更加方便地处理类和对象,本篇文章将详细介绍 TypeScript 装饰器和反射元数据。


1、TypeScript 装饰器

TypeScript 装饰器是一种特殊类型的声明,它可以被附加到类声明、方法、属性或参数上,以修改类的行为。装饰器是一种函数,它可以接受一个或多个参数,并可以返回一个值。在 TypeScript 中,装饰器通过 @ 符号来表示。

我们来看一个简单的例子,假设我们有一个类 Greeter,它有一个方法 greet:

  1. class Greeter {
  2. greeting: string;
  3. constructor(message: string) {
  4. this.greeting = message;
  5. }
  6. greet() {
  7. return "Hello, " + this.greeting;
  8. }
  9. }

现在我们想要在 greet 方法执行之前和之后打印一些日志,我们可以使用装饰器来实现:

  1. function log(target: any, key: string, descriptor: PropertyDescriptor) {
  2. const originalMethod = descriptor.value;
  3. descriptor.value = function (...args: any[]) {
  4. console.log(`Call ${key} with arguments: ${args}`);
  5. const result = originalMethod.apply(this, args);
  6. console.log(`Result: ${result}`);
  7. return result;
  8. }
  9. return descriptor;
  10. }
  11. class Greeter {
  12. greeting: string;
  13. constructor(message: string) {
  14. this.greeting = message;
  15. }
  16. @log
  17. greet() {
  18. return "Hello, " + this.greeting;
  19. }
  20. }

在上面的例子中,我们定义了一个名为 log 的装饰器函数,它接受三个参数:目标类的原型对象 target,方法名 key 和属性描述符 descriptor。在装饰器函数内部,我们将原始的方法保存到 originalMethod 中,然后修改 descriptor.value,使其在调用原始方法前后打印一些日志。最后,我们返回修改后的 descriptor。

然后,在 greet 方法前加上装饰器 @log,我们就可以在调用 greet 方法时看到日志输出了:

  1. const greeter = new Greeter('world');
  2. greeter.greet();

输出:

  1. Call greet with arguments:
  2. Result: Hello, world

这样,我们就成功地使用装饰器修改了类的行为,这是非常有用的。

2、反射元数据

反射元数据是指关于类型的信息,包括类、接口、函数、方法等的描述信息,比如名称、参数、返回值等等。在 TypeScript 中,我们可以使用反射元数据来获取类和对象的信息,从而实现一些高级的功能。

TypeScript 通过 Reflect 对象提供了一组 API 来操作反射元数据。下面是一些常用的 API:

  • Reflect.getMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): any:获取指定对象的元数据。其中 metadataKey 是元数据的键值,target 是对象的原型或构造函数,propertyKey 是可选的属性名称。
  • Reflect.defineMetadata(metadataKey: any, metadataValue: any, target: any, propertyKey?: string | symbol): void:定义指定对象的元数据。其中 metadataKey 是元数据的键值,metadataValue 是元数据的值,target 是对象的原型或构造函数,propertyKey 是可选的属性名称。
  • Reflect.hasMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean:判断指定对象是否有指定的元数据。其中 metadataKey 是元数据的键值,target 是对象的原型或构造函数,propertyKey 是可选的属性名称。
  • Reflect.metadata(metadataKey: any, metadataValue: any): ClassDecorator & PropertyDecorator:创建一个装饰器,用于定义类和属性的元数据。其中 metadataKey 是元数据的键值,metadataValue 是元数据的值。

下面我们来看一个具体的例子,假设我们有一个类 Person,它有一个属性 name:

  1. class Person {
  2. @Reflect.metadata('description', 'This is a person's name')
  3. name: string;
  4. constructor(name: string) {
  5. this.name = name;
  6. }
  7. }

在上面的例子中,我们使用 Reflect.metadata 装饰器来定义了属性 name 的元数据。元数据的键值是 ‘description’,值是 'This is a person's name'

然后,我们可以使用 Reflect.getMetadata 方法来获取这个元数据:

  1. const description = Reflect.getMetadata('description', Person.prototype, 'name');
  2. console.log(description); // 输出:This is a person's name

在上面的例子中,我们使用 Reflect.getMetadata 方法来获取类 Person 原型对象的属性 name 的元数据。由于 Reflect.metadata 装饰器创建的元数据是针对类和属性的,所以我们需要传递 Person.prototype 和 ‘name’ 作为参数。

通过反射元数据,我们可以在运行时动态地获取类和对象的信息,从而实现一些高级的功能,比如依赖注入、类型转换等等。

3、TypeScript 装饰器与反射元数据的结合使用

TypeScript 装饰器和反射元数据是非常强大的功能,它们可以结合使用来实现一些高级的功能。下面是一个示例,演示如何使用 TypeScript 装饰器和反射元数据来实现依赖注入:

  1. interface Injectable {
  2. name: string;
  3. }
  4. class Foo implements Injectable {
  5. name = 'Foo';
  6. }
  7. class Bar implements Injectable {
  8. name = 'Bar';
  9. }
  10. const injectables = new Map<string, Injectable>();
  11. injectables.set('Foo', Foo);
  12. injectables.set('Bar', Bar);
  13. function Injectable(name: string) {
  14. return function (target: any) {
  15. Reflect.defineMetadata('injectable', { name }, target);
  16. };
  17. }
  18. function Inject(target: any, propertyKey: string) {
  19. const metadata = Reflect.getMetadata('design:type', target, propertyKey);
  20. const injectableMetadata = Reflect.getMetadata('injectable', metadata.prototype);
  21. const injectable = injectables.get(injectableMetadata.name);
  22. target[propertyKey] = new injectable();
  23. }
  24. @Injectable('Foo')
  25. class MyClass {
  26. @Inject
  27. foo: Foo;
  28. doSomething() {
  29. console.log(this.foo.name);
  30. }
  31. }
  32. const myClass = new MyClass();
  33. myClass.doSomething(); // 输出:Foo

在上面的示例中,我们定义了两个类 Foo 和 Bar,它们都实现了 Injectable 接口,并被添加到了一个 Map 中,用于后续的依赖注入。

然后,我们定义了两个装饰器 Injectable 和 Inject。其中,Injectable 装饰器用于定义类的元数据,Inject 装饰器用于定义属性的元数据。

在 Injectable 装饰器中,我们使用 Reflect.defineMetadata 方法来定义类的元数据,包括它的名称等信息。在 Inject 装饰器中,我们使用 Reflect.getMetadata 方法来获取属性的类型和类的元数据,然后根据元数据的信息从 Map 中获取相应的类,并创建一个实例赋值给属性。

最后,定义了一个 MyClass 类,并在其中使用 Inject 装饰器来注入一个 Foo 实例。在 doSomething 方法中,我们可以通过 this.foo 访问到注入的实例。

通过上面的示例,我们可以看到,使用 TypeScript 装饰器和反射元数据可以非常方便地实现依赖注入,从而实现解耦和复用的目的。

小结
本章节绍 TypeScript 装饰器和反射元数据的概念和用法,以及如何结合使用来实现一些高级的功能,比如依赖注入。通过本文的学习,可以深入了解 TypeScript 的高级特性,并且掌握如何使用 TypeScript 装饰器和反射元数据来实现依赖注入等功能。


该分类下的相关小册推荐: