Reflect Metadata
更新时间:2023-02-22 15:57:53标签:typescriptreflectweb前端
什么是Reflect metadata
-
其他 C#、Java、Pythone 语言已经有的高级功能,我 JS 也应该要有(诸如C# 和 Java 之类的语言支持将元数据添加到类型的属性或注释,以及用于读取元数据的反射API,而目前 JS 缺少这种能力)
-
许多用例(组合/依赖注入,运行时类型断言,反射/镜像,测试)都希望能够以一致的方式向类中添加其他元数据。
-
为了使各种工具和库能够推理出元数据,需要一种标准一致的方法;
-
元数据不仅可以用在对象上,也可以通过相关捕获器用在 Proxy 上
-
对开发人员来说,定义新的元数据生成装饰器应该简洁易用;
TS配置
1{2 "compilerOptions": {3 "experimentalDecorators": true,4 "emitDecoratorMetadata": true5 }6}
安装reflect-metadata
npm install reflect-metadata
示例
依赖注入 DI
1import 'reflect-metadata';23interface Constructor<T=any> {4 new (...args: any[]): T;5}67const Injectable = (): ClassDecorator => () => {8 //9};1011@Injectable()12class OtherService {13 public method() {14 return 'this is other service';15 }16}1718@Injectable()19class AbilityService {20 constructor(21 private other: OtherService,22 ) {}23 public speak() {24 return `${this.other.method()} and 汪汪汪`;25 }26}2728@Injectable()29class DogService {30 constructor(31 private abilities: AbilityService,32 ) {}33 public speak() {34 return this.abilities.speak();35 }36}3738export { DogService };3940export 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';23interface Constructor<T=any> {4 new (...args: any[]): T;5}67const PATH_MATA = 'path';8const METHOD_MATA = 'method';9const INJECTABLE = Symbol('Injectable');10const ROUTE = Symbol('Route');1112const 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};2122const Controller = (basePath: string): ClassDecorator => target => {23 Injectable()(target);24 Reflect.defineMetadata(PATH_MATA, basePath, target);25};2627const 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};3536const Get = createRoute('get');37const Post = createRoute('post');3839function 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}5455export 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}6364@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}7576export const test = mappingRoutes(factory(TestController));