TypeScript系列教程十一《装饰器》 -- reflect-metadata

2022-05-06 17:20:20 浏览数 (1)

reflect-metadata 是ES7 的提案 ,TypeScript 1.5 已经开始使用。reflect-metadata是一个单独的npm 包,具体介绍可以看看官方介绍。

系列教程

  • TypeScript系列教程一《开篇》
  • TypeScript系列教程二《安装起步》
  • TypeScript系列教程三《基础类型》
  • TypeScript系列教程四《扩展类型》
  • TypeScript系列教程五《对象类型》》
  • TypeScript系列教程六《泛型》
  • TypeScript系列教程七《接口》
  • TypeScript系列教程八《类》
  • TypeScript系列教程九《高级类型》
  • TypeScript系列教程九《类型转换》-- keyof和typeof 操作
  • TypeScript系列教程九《类型转换》-- 索引访问类型
  • TypeScript系列教程九《类型转换》-- 条件类型
  • TypeScript系列教程九《类型转换》-- 映射类型
  • TypeScript系列教程九《类型转换》-- 条件类型
  • TypeScript系列教程九《类型转换》-- 模板文本类型
  • TypeScript系列教程十《模块》
  • TypeScript系列教程十一《装饰器》 – 装饰器与继承
  • TypeScript系列教程十一《装饰器》 – 类装饰器
  • TypeScript系列教程十一《装饰器》 – 方法装饰器
  • TypeScript系列教程十一《装饰器》 – reflect-metadata
  • TypeScript系列教程十一《装饰器》 – 属性装饰器
  • TypeScript系列教程十一《装饰器》 – 参数装饰器

reflect-metadata 拆成两个单词,reflect 反射和 metadata,通俗理解 利用反射的原理修改元数据。

元数据就是配置数据的数据,reflect-metadata 利用反射的原理通过key、value的形式给对象、对象属性设置数据,从而不改变其数据结构。

安装配置

首先我们需要单独引入这个包:

代码语言:javascript复制
npm install reflect-metadata

在tsconfig里打开下面属性支持装饰器和元数据:

代码语言:javascript复制
  /* Enables experimental support for ES7 decorators.*/
  "experimentalDecorators": true, 
 /* Enables experimental support for emitting type metadata for decorators. */
  "emitDecoratorMetadata": true,  

API

反射元数据可以在对象或者对象属性上添加元数据,提供装饰器在类的原型对象和对象属性上添加元数据。

代码语言:javascript复制
//在对象或属性上定义源数据
Reflect.defineMetadata(metadataKey, metadataValue, target);
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);

// 检查某个源数据的 key 是否存在某个对象或属性上
let result = Reflect.hasMetadata(metadataKey, target);
let result = Reflect.hasMetadata(metadataKey, target, propertyKey);

// 检查是否有自带源数据 key 存在某个对象或属性上
let result = Reflect.hasOwnMetadata(metadataKey, target);
let result = Reflect.hasOwnMetadata(metadataKey, target, propertyKey);

// 通过 key 在对象或属性的原型链上获取源数据的值
let result = Reflect.getMetadata(metadataKey, target);
let result = Reflect.getMetadata(metadataKey, target, propertyKey);

// 通过 自带源数据 key 在对象或属性的原型链上获取源数据的值
let result = Reflect.getOwnMetadata(metadataKey, target);
let result = Reflect.getOwnMetadata(metadataKey, target, propertyKey);

// 获取对象或属性原型链上的所有源数据
let result = Reflect.getMetadataKeys(target);
let result = Reflect.getMetadataKeys(target, propertyKey);

// 获取对象或属性上所有自带的源数据 keys
let result = Reflect.getOwnMetadataKeys(target);
let result = Reflect.getOwnMetadataKeys(target, propertyKey);

// 在对象或属性上删除源数据
let result = Reflect.deleteMetadata(metadataKey, target);
let result = Reflect.deleteMetadata(metadataKey, target, propertyKey);

//通过修饰器在构造函数上应用源数据
@Reflect.metadata(metadataKey, metadataValue)
class C {

  // 通过修饰器在方法或属性上应用源数据
  @Reflect.metadata(metadataKey, metadataValue)
  method() {
  }
}

我们已经在 tsconfig.json 中开启了 emitDecoratorMetadata 选项,此时,TypeScript 在编译时定义一些 元数据设计键,目前可用的有:

  • 属性类型元数据 design:type :用于获取类属性的类型
  • 参数类型元数据 design:paramtypes:用于获取方法参数的类型
  • 返回类型元数据 design:returntype:用于获取返回值的类型

目前只有这三个设计键可用,但已经足够覆盖大部分常见场景了。

说了那么多概念,那么这个reflect-matedata 有什么用呢?有哪些使用场景?

使用场景

reflect-matedata 光看api很容易明白,使用也简单,主要是思想和使用场景比较抽象,在什么时候可以使用到他呢?下面总结了几个。

参数统一处理

主要是想统一替换实例,拦截然后重新插入。

示例主要演示了,不论接收什么参数,都可以重新拦截修改注入。

  • 方法装饰器
  • 根据 reflect-matedata design:paramtypes 拿到方法参数类型
  • 根据类型实例化修改然后重新注入

代码:

无论我传男生还是女生,我都统一拦截处理修改成了中性。

代码语言:javascript复制
import "reflect-metadata";

class People {
  sex:string
} 

class Women implements People{

  sex: string = '女生'
}

class Man implements People{

  sex: string = '男生'
}


function SexDecorate(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor){
  let PropsTypes = Reflect.getMetadata('design:paramtypes',target,propertyKey)
  let p =   new  PropsTypes[0]()
  p.sex = '中性'
  let orgMethod = descriptor.value 
  descriptor.value = ()=>{
    orgMethod(p)
  }
}

class Student {
 
  
  @SexDecorate
  getSex(obj:People){
    console.log(obj);
    
  }
  
}

let man = new Man()
let women = new Women()

let std = new Student()
std.getSex(man)
std.getSex(women)

打印结果:

控制反转和依赖注入

在Angular 和nestjs 中有大量的注入,这也是我从新学习装饰器的目的,就是看懂nestjs 代码。

说这个之前,先做个试验:

代码语言:javascript复制
class Tes {
    constructor(a:string) {
    }
    run(name:string){
    }
}
console.log(Reflect.getMetadata('design:paramtypes', Tes));

我要通过 Reflect.getMetadata 去构造函数参数,这样是取不出来的,必须加上装饰器。

我加一个装饰器就可以取出来了:

代码语言:javascript复制
const a:()=>ClassDecorator = ()=>{
    return (target:Function)=>{

    }
}

@a()
class Tes {
    constructor(a:string) {
        
    }
    run(name:string){

    }
}

  console.log(Reflect.getMetadata('design:paramtypes', Tes));

下面看一段简单的注入实现代码:

代码语言:javascript复制
import "reflect-metadata";
// 构造函数类型,注入的依赖必须是可以按照这个构造函数构造的。
type Constructor<T = any> = new (...args: any[]) => T;
// 注入依赖装饰器,为了能取到元数据参数。
const Injectable = (): ClassDecorator => target => {};

class OtherService {
  a = 1;
}

@Injectable()
class TestService {
  constructor(public readonly otherService: OtherService) {}

  testMethod() {
    console.log(this.otherService.a);
  }
}


const Factory = <T>(target: Constructor<T>): T => {
    // 获取所有注入的服务
    const providers = Reflect.getMetadata('design:paramtypes', target); // [OtherService]
    console.log(providers);
     // 根据构造函数类型实例化
    const args = providers.map((provider: Constructor) => new provider());
    //构造服务并将构造依赖示例参数传入
    return new target(...args);
  };
  
  Factory(TestService).testMethod(); // 1

到此结束,下面关于参数和属性装饰器示例还要用到reflect-metadata。

0 人点赞