Reflect Metadata

更新时间:2023-02-22 15:57:53标签:typescriptreflectweb前端

什么是Reflect metadata

  1. 其他 C#、Java、Pythone 语言已经有的高级功能,我 JS 也应该要有(诸如C# 和 Java 之类的语言支持将元数据添加到类型的属性或注释,以及用于读取元数据的反射API,而目前 JS 缺少这种能力)

  2. 许多用例(组合/依赖注入,运行时类型断言,反射/镜像,测试)都希望能够以一致的方式向类中添加其他元数据。

  3. 为了使各种工具和库能够推理出元数据,需要一种标准一致的方法;

  4. 元数据不仅可以用在对象上,也可以通过相关捕获器用在 Proxy 上

  5. 对开发人员来说,定义新的元数据生成装饰器应该简洁易用;

TS配置

1{
2 "compilerOptions": {
3 "experimentalDecorators": true,
4 "emitDecoratorMetadata": true
5 }
6}

安装reflect-metadata

npm install reflect-metadata

示例

依赖注入 DI

1import 'reflect-metadata';
2
3interface Constructor<T=any> {
4 new (...args: any[]): T;
5}
6
7const Injectable = (): ClassDecorator => () => {
8 //
9};
10
11@Injectable()
12class OtherService {
13 public method() {
14 return 'this is other service';
15 }
16}
17
18@Injectable()
19class AbilityService {
20 constructor(
21 private other: OtherService,
22 ) {}
23 public speak() {
24 return `${this.other.method()} and 汪汪汪`;
25 }
26}
27
28@Injectable()
29class DogService {
30 constructor(
31 private abilities: AbilityService,
32 ) {}
33 public speak() {
34 return this.abilities.speak();
35 }
36}
37
38export { DogService };
39
40export function factory<T>(target: Constructor<T>): T {
41 const providers = Reflect.getMetadata('design:paramtypes', target) || [];
42 const args = providers.map((provider: Constructor) => factory(provider));
43 return new target(...args);
44}
const dog = factory(DogService);
dog.speak();

output:this is other service and 汪汪汪

Controller 控制器

1import 'reflect-metadata';
2
3interface Constructor<T=any> {
4 new (...args: any[]): T;
5}
6
7const PATH_MATA = 'path';
8const METHOD_MATA = 'method';
9const INJECTABLE = Symbol('Injectable');
10const ROUTE = Symbol('Route');
11
12const Injectable = (): ClassDecorator => target => {
13 Reflect.defineMetadata(INJECTABLE, true, target);
14};
15const isInjectable = <T>(constructor: Constructor<T>) => {
16 return !!Reflect.getMetadata(INJECTABLE, constructor);
17};
18const isRoute = (target: any) => {
19 return !!Reflect.getMetadata(ROUTE, target);
20};
21
22const Controller = (basePath: string): ClassDecorator => target => {
23 Injectable()(target);
24 Reflect.defineMetadata(PATH_MATA, basePath, target);
25};
26
27const createRoute = (method: string) => (path: string): MethodDecorator => (target, propertyKey, descriptor: TypedPropertyDescriptor<any>) => {
28 const type = Reflect.getMetadata('design:type', target, propertyKey);
29 if (type === Function) {
30 Reflect.defineMetadata(ROUTE, true, descriptor.value);
31 Reflect.defineMetadata(METHOD_MATA, method, descriptor.value);
32 Reflect.defineMetadata(PATH_MATA, path, descriptor.value);
33 }
34};
35
36const Get = createRoute('get');
37const Post = createRoute('post');
38
39function mappingRoutes(instance: any) {
40 const prototype = Object.getPrototypeOf(instance);
41 const methodNames = Object.getOwnPropertyNames(prototype).filter(name => isRoute(prototype[name]));
42 const basePath = Reflect.getMetadata(PATH_MATA, instance.constructor);
43 return methodNames.map(name => {
44 const fn = prototype[name];
45 return {
46 basePath,
47 method: Reflect.getMetadata(METHOD_MATA, fn),
48 route: Reflect.getMetadata(PATH_MATA, fn),
49 fn,
50 name,
51 };
52 });
53}
54
55export function factory<T>(target: Constructor<T>): T {
56 if (!isInjectable(target)) {
57 throw new Error(`${target.name} must be injectable`);
58 }
59 const providers = Reflect.getMetadata('design:paramtypes', target) || [];
60 const args = providers.map((provider: Constructor) => factory(provider));
61 return new target(...args);
62}
63
64@Controller('/api')
65class TestController {
66 @Get('/list')
67 getList() {
68 return 'this is a list';
69 }
70 @Post('/create')
71 create() {
72 return 'create an item';
73 }
74}
75
76export const test = mappingRoutes(factory(TestController));