• ES7 Reflect Metadata


    https://jkchao.github.io/typescript-book-chinese/tips/metadata.html

    Reflect Metadata

    #基础

    Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。TypeScript 在 1.5+ 的版本已经支持它,你只需要:

    • npm i reflect-metadata --save
    • 在 tsconfig.json 里配置 emitDecoratorMetadata 选项。

    Reflect Metadata 的 API 可以用于类或者类的属性上,如:

    function metadata(
      metadataKey: any,
      metadataValue: any
    ): {
      (target: Function): void;
      (target: Object, propertyKey: string | symbol): void;
    };
    

    Reflect.metadata 当作 Decorator 使用,当修饰类时,在类上添加元数据,当修饰类属性时,在类原型的属性上添加元数据,如:

    @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'
    

    它具有诸多使用场景。

    #获取类型信息

    譬如在 vue-property-decorator 6.1 及其以下版本中,通过使用 Reflect.getMetadata API,Prop Decorator 能获取属性类型传至 Vue,简要代码如下:

    function Prop(): PropertyDecorator {
      return (target, key: string) => {
        const type = Reflect.getMetadata('design:type', target, key);
        console.log(`${key} type: ${type.name}`);
        // other...
      };
    }
    
    class SomeClass {
      @Prop()
      public Aprop!: string;
    }
    

    运行代码可在控制台看到 Aprop type: string。除能获取属性类型外,通过 Reflect.getMetadata("design:paramtypes", target, key) 和 Reflect.getMetadata("design:returntype", target, key) 可以分别获取函数参数类型和返回值类型。

    #自定义 metadataKey

    除能获取类型信息外,常用于自定义 metadataKey,并在合适的时机获取它的值,示例如下:

    function classDecorator(): ClassDecorator {
      return target => {
        // 在类上定义元数据,key 为 `classMetaData`,value 为 `a`
        Reflect.defineMetadata('classMetaData', 'a', target);
      };
    }
    
    function methodDecorator(): MethodDecorator {
      return (target, key, descriptor) => {
        // 在类的原型属性 'someMethod' 上定义元数据,key 为 `methodMetaData`,value 为 `b`
        Reflect.defineMetadata('methodMetaData', 'b', target, key);
      };
    }
    
    @classDecorator()
    class SomeClass {
      @methodDecorator()
      someMethod() {}
    }
    
    Reflect.getMetadata('classMetaData', SomeClass); // 'a'
    Reflect.getMetadata('methodMetaData', new SomeClass(), 'someMethod'); // 'b'
    

    #例子

    #控制反转和依赖注入

    在 Angular 2+ 的版本中,控制反转与依赖注入便是基于此实现,现在,我们来实现一个简单版:

    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]
      const args = providers.map((provider: Constructor) => new provider());
      return new target(...args);
    };
    
    Factory(TestService).testMethod(); // 1
    

    #Controller 与 Get 的实现

    如果你在使用 TypeScript 开发 Node 应用,相信你对 ControllerGetPOST 这些 Decorator,并不陌生:

    @Controller('/test')
    class SomeClass {
      @Get('/a')
      someGetMethod() {
        return 'hello world';
      }
    
      @Post('/b')
      somePostMethod() {}
    }
    

    这些 Decorator 也是基于 Reflect Metadata 实现,这次,我们将 metadataKey 定义在 descriptor 的 value 上:

    const METHOD_METADATA = 'method';
    const PATH_METADATA = 'path';
    
    const Controller = (path: string): ClassDecorator => {
      return target => {
        Reflect.defineMetadata(PATH_METADATA, path, target);
      }
    }
    
    const createMappingDecorator = (method: string) => (path: string): MethodDecorator => {
      return (target, key, descriptor) => {
        Reflect.defineMetadata(PATH_METADATA, path, descriptor.value);
        Reflect.defineMetadata(METHOD_METADATA, method, descriptor.value);
      }
    }
    
    const Get = createMappingDecorator('GET');
    const Post = createMappingDecorator('POST');
    

    接着,创建一个函数,映射出 route

    function mapRoute(instance: Object) {
      const prototype = Object.getPrototypeOf(instance);
    
      // 筛选出类的 methodName
      const methodsNames = Object.getOwnPropertyNames(prototype)
                                  .filter(item => !isConstructor(item) && isFunction(prototype[item]));
      return methodsNames.map(methodName => {
        const fn = prototype[methodName];
    
        // 取出定义的 metadata
        const route = Reflect.getMetadata(PATH_METADATA, fn);
        const method = Reflect.getMetadata(METHOD_METADATA, fn);
        return {
          route,
          method,
          fn,
          methodName
        }
      })
    };
    

    因此,我们可以得到一些有用的信息:

    Reflect.getMetadata(PATH_METADATA, SomeClass); // '/test'
    
    mapRoute(new SomeClass());
    
    /**
     * [{
     *    route: '/a',
     *    method: 'GET',
     *    fn: someGetMethod() { ... },
     *    methodName: 'someGetMethod'
     *  },{
     *    route: '/b',
     *    method: 'POST',
     *    fn: somePostMethod() { ... },
     *    methodName: 'somePostMethod'
     * }]
     *
     */
    

    最后,只需把 route 相关信息绑在 express 或者 koa 上就 ok 了。

  • 相关阅读:
    联考20200520 T2 函数
    联考20200520 T1 石子游戏
    模拟赛T2 中继系统
    模拟赛T2 仙人掌毒题
    BZOJ3462 DZY Loves Math II
    20200129模拟赛T1 string
    BZOJ1316 树上的询问
    BZOJ4559 成绩比较
    JZOJ4238 纪念碑
    BZOJ 2648 世界树
  • 原文地址:https://www.cnblogs.com/sunupo/p/15967609.html
Copyright © 2020-2023  润新知