reflect-metadata 是ES7 的提案 ,TypeScript 1.5 已经开始使用。reflect-metadata是一个单独的npm 包,具体介绍可以看看官方介绍。
reflect-metadata 拆成两个单词,reflect 反射和 metadata,通俗理解 利用反射的原理修改元数据。
元数据就是配置数据的数据,reflect-metadata 利用反射的原理通过key、value的形式给对象、对象属性设置数据,从而不改变其数据结构。
安装配置
首先我们需要单独引入这个包:
npm install reflect-metadata
在tsconfig里打开下面属性支持装饰器和元数据:
/* Enables experimental support for ES7 decorators.*/
"experimentalDecorators": true,
/* Enables experimental support for emitting type metadata for decorators. */
"emitDecoratorMetadata": true,
API
反射元数据可以在对象或者对象属性上添加元数据,提供装饰器在类的原型对象和对象属性上添加元数据。
//在对象或属性上定义源数据
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 拿到方法参数类型
- 根据类型实例化修改然后重新注入
代码:
无论我传男生还是女生,我都统一拦截处理修改成了中性。
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 代码。
说这个之前,先做个试验:
class Tes {
constructor(a:string) {
}
run(name:string){
}
}
console.log(Reflect.getMetadata('design:paramtypes', Tes));
我要通过 Reflect.getMetadata 去构造函数参数,这样是取不出来的,必须加上装饰器。
我加一个装饰器就可以取出来了:
const a:()=>ClassDecorator = ()=>{
return (target:Function)=>{
}
}
@a()
class Tes {
constructor(a:string) {
}
run(name:string){
}
}
console.log(Reflect.getMetadata('design:paramtypes', Tes));
下面看一段简单的注入实现代码:
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。