Reflect Metadata - TypeScript 元数据的理解与使用

2,367 阅读3分钟

元数据

元数据是用来定义数据的数据。例如,对于一个数据A,它会具有值,数据类型等等描述这个数据的数据。这样的数据,我们称之为元数据。

在 JS 当中,也就是所谓的装饰器。通过反射来获取类属性上面的批注。但是 JS 的装饰器更多的是存在于对函数或者属性进行一些操作,比如修改他们的值,代理变量,自动绑定 this 等等功能。但是却无法实现通过反射来获取究竟有哪些装饰器添加到这个类/方法上,这时候就需要 Reflect Metadata 。

Relfect Metadata

使用 Relfect Metadata,你可以通过装饰器来给类添加一些自定义的信息。然后通过反射将这些信息提取出来。当然你也可以通过反射来添加这些信息。

import "reflect-metadata";

@Reflect.metadata('inClass', 'A') 
class Test {
    @Reflect.metadata('inMethod', 'B')
    public hello(): string {
        return 'hello world'; 
    }
 }
console.log(Reflect.getMetadata('inClass', Test)); // 'A'
console.log(Reflect.getMetadata('inMethod', new Test(), 'hello')); // 'B'

获取类型信息

获取属性类型

Reflect.getMetadata('design:type', target, key):获取属性类型

import "reflect-metadata";

function logType(target: any, key: string) {
  const t = Reflect.getMetadata("design:type", target, key);
  console.log(`${key} type: ${t.name}`); // 会打印出 attr1 type: String 
}

class Demo {
  @logType
  public attr1 : string; 
}

获取函数参数类型

Reflect.getMetadata("design:paramtypes", target, key):获取函数参数类型

import "reflect-metadata";

function logParamTypes(target: any, key: string) {
  const types = Reflect.getMetadata("design:paramtypes", target, key);
  const s = types.map(a => a ? a.name : 'undefined').join('\n  ');
  console.log(`${key} param types: ${s}`);
  
  // 打印出:
  // doSomething param types: 
  //   Number
  //    String
  //    Boolean
  //    Object
  //    undefined
  //    Array
  //    Array
  //    Foo
  //    String
  //    Function
  //    Object
}
class Foo {}
interface IFoo {}
enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}
class Demo {
  @logParamTypes 
  doSomething(
    param1number,
    param2string,
    param3boolean,
    param4any,
    param5void,
    param6: [],
    param7: [stringnumber],
    param8Foo,
    param9Direction,
    param10() => 0,
    param11IFoo,
  ): number {
    return 1;
  }
}

/**
    序列化规则
    number 序列化为 Number
    string 序列化为 String
    boolean 序列化为 Boolean
    any 序列化为 Object
    void 序列化为 undefined
    Array 序列化为 Array
    Tuple序列化为 Array
    class 序列化为类构造函数
    Enum 序列化为 Number
    如果至少有一个签名,则序列化为 Function
    否则序列化为 Object(包括接口)
*/

获取函数返回值类型

Reflect.getMetadata("design:returntype", target, key):获取函数返回值类型

import "reflect-metadata";

function returnType(target: any, key: string) {
  const type = Reflect.getMetadata("design:returntype", target, key);
  console.log(`${key} return type: ${type.name}`);
  // 打印出:doSomething return type: Number
}
class Demo {
  @returnType 
  doSomething(paramnumber): number {
    return 1;
  }
}

自定义元数据

用途

其实所有的用途都是一个目的,给对象添加额外的信息,但是不影响对象的结构。这一点很重要,当你给对象添加了一个元信息的时候,对象是不会有任何的变化的,不会多 property,也不会有的 property 被修改了。可以衍生出很多其他的用途。

API

namespace Reflect {
  // 用于装饰器
  function metadata(metadataKey: any, metadataValue: any): {
        (targetFunction): void;
        (targetObjectpropertyKeystring | symbol): void;
    };
  
  // 在对象或属性上面定义元数据
  function defineMetadata(metadataKey: any, metadataValue: any, target: Object): void;
  function defineMetadata(metadataKey: any, metadataValue: any, target: Object, propertyKey: string | symbol): void;
  
  // 检查对象或属性的原型链上是否存在元数据
  function hasMetadata(metadataKey: any, target: Object): boolean;
  function hasMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;
  
  // 检查对象或属性的原型链上是否存在自己的元数据
  function hasOwnMetadata(metadataKey: any, target: Object): boolean;
  function hasOwnMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;
  
  // 获取对象或属性上的元数据键的元数据值
  function getMetadata(metadataKey: any, target: Object): any;
  function getMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): any;
  
  // 获取对象或属性上自己的元数据键的元数据值
  function getOwnMetadata(metadataKey: any, target: Object): any;
  function getOwnMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): any;
  
  // 获取对象或属性原型链上的所有元数据键
  function getMetadataKeys(target: Object): any[];
  function getMetadataKeys(target: Object, propertyKey: string | symbol): any[];
  
  // 获取对象或属性的所有自己的元数据键
  function getOwnMetadataKeys(target: Object): any[];
  function getOwnMetadataKeys(target: Object, propertyKey: string | symbol): any[];
  
  // 从对象或属性中删除元数据
  function deleteMetadata(metadataKey: any, target: Object): boolean;
  function deleteMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;
}

// 需要在 tsconfig.json 配置的开关:
{
    "experimentalDecorators"true"emitDecoratorMetadata"true,     
} 

注: 类似于类的继承,查找元数据的方式也是通过原型链进行的。

class A {
  @Reflect.metadata('name', 'hello')
  hello() {}
}

const t1 = new A()
const t2 = new A()
Reflect.defineMetadata('otherName', 'world', t2, 'hello')
Reflect.getMetadata('name', t1, 'hello') // 'hello'
Reflect.getMetadata('name', t2, 'hello') // 'hello'
Reflect.getMetadata('otherName', t2, 'hello') // 'world'

Reflect.getOwnMetadata('name', t2, 'hello') // undefined
Reflect.getOwnMetadata('otherName', t2, 'hello') // 'world'