reflect-metadata 的用法进行系统性、全覆盖的讲解,内容涵盖以下结构:


🧠 一、基础概念与原理

1.1 什么是 Reflect Metadata?

  • ECMAScript 的 Reflect API 原本提供的是对对象操作的反射能力,如 Reflect.get()
  • reflect-metadata 扩展了 Reflect,允许你为类、属性、参数、方法附加元数据(metadata),并在运行时获取这些信息。

这在 JavaScript 本身是做不到的,TypeScript 提供了 emitDecoratorMetadata 选项,让它成为可能。

1.2 元数据的作用

  • 在框架设计中用于:依赖注入、路由注册、权限控制、序列化、验证等。
  • 特别适合用于**装饰器(Decorator)**中,通过装饰器记录额外信息。

🧪 二、完整 API 说明

方法

描述

Reflect.defineMetadata(metadataKey, metadataValue, target, [propertyKey])

定义元数据

Reflect.hasMetadata(metadataKey, target, [propertyKey])

是否存在元数据

Reflect.getMetadata(metadataKey, target, [propertyKey])

获取元数据

Reflect.deleteMetadata(metadataKey, target, [propertyKey])

删除元数据

Reflect.getOwnMetadata()

getMetadata 类似,但只获取自身定义的,不包含继承的


🛠️ 三、装饰器中常用的 TypeScript 自动注入类型元数据

当启用 emitDecoratorMetadata,可以通过以下 metadata key 获取类型信息:

metadata key

用途

design:type

属性类型

design:paramtypes

方法参数类型(数组)

design:returntype

方法返回类型


📘 四、用法全案例详解

✅ 4.1 装饰属性并获取其类型

import 'reflect-metadata';class Demo {@Reflect.metadata('custom:desc', '用户名字段')name: string;
}const desc = Reflect.getMetadata('custom:desc', Demo.prototype, 'name');
console.log(desc); // 用户名字段

✅ 4.2 获取属性类型(design:type

import 'reflect-metadata';class A {name: string;
}const type = Reflect.getMetadata('design:type', A.prototype, 'name');
console.log(type.name); // String

✅ 4.3 获取方法参数和返回值类型

import 'reflect-metadata';class B {add(a: number, b: number): number {return a + b;}
}console.log(Reflect.getMetadata('design:paramtypes', B.prototype, 'add')); // [ [Function: Number], [Function: Number] ]
console.log(Reflect.getMetadata('design:returntype', B.prototype, 'add')); // [Function: Number]

✅ 4.4 参数装饰器收集类型

function Log(target: Object, propertyKey: string | symbol, parameterIndex: number) {const types = Reflect.getMetadata('design:paramtypes', target, propertyKey);console.log(`参数[${parameterIndex}]类型为: ${types[parameterIndex].name}`);
}class C {hello(@Log name: string) {}
}
// 输出:参数[0]类型为: String

✅ 4.5 存储方法级元数据 + 遍历

function Route(method: string) {return (target: Object, propertyKey: string) => {Reflect.defineMetadata('route', method, target, propertyKey);};
}class Controller {@Route('GET')getData() {}@Route('POST')saveData() {}
}for (const key of Object.getOwnPropertyNames(Controller.prototype)) {const method = Reflect.getMetadata('route', Controller.prototype, key);if (method) {console.log(`${key} => ${method}`);}
}
// 输出:
// getData => GET
// saveData => POST

✅ 4.6 类构造函数参数类型反射(DI 原理)

class Service {}class Controller {constructor(public service: Service) {}
}const paramTypes = Reflect.getMetadata('design:paramtypes', Controller);
console.log(paramTypes[0].name); // Service

🔄 五、继承中的元数据行为

class Parent {@Reflect.metadata('tag', 'parent')name: string;
}class Child extends Parent {}console.log(Reflect.getMetadata('tag', Child.prototype, 'name') // parent
);
console.log(Reflect.getOwnMetadata('tag', Child.prototype, 'name') // undefined
);

getMetadata 会递归查找继承链,getOwnMetadata 只查当前。


📦 六、实际场景总结

场景

如何应用

NestJS 的 DI

获取构造函数参数类型,自动实例化

权限控制

方法上标记元数据(如 @Role('admin')

路由注册

@Get() 装饰器存入路由表元数据

ORM 映射

标记字段的类型和数据库映射

class-validator

装饰器读取字段类型+校验规则


⚠️ 七、使用建议

  • 总是配合 TypeScript 装饰器使用,单独使用不推荐。
  • 开发大型框架时,reflect-metadata 是构建装饰器体系的底层核心。
  • 注意类型丢失问题,接口类型无法反射(接口编译后会被擦除)。

🏁 八、环境准备 & tsconfig 配置

{"compilerOptions": {"target": "ES2020","module": "commonjs","emitDecoratorMetadata": true,"experimentalDecorators": true,"strict": true,"esModuleInterop": true}
}
// main.ts
import 'reflect-metadata';