reflect-metadata
的用法进行系统性、全覆盖的讲解,内容涵盖以下结构:
🧠 一、基础概念与原理
1.1 什么是 Reflect Metadata?
- ECMAScript 的
Reflect
API 原本提供的是对对象操作的反射能力,如Reflect.get()
。 -
reflect-metadata
扩展了Reflect
,允许你为类、属性、参数、方法附加元数据(metadata),并在运行时获取这些信息。
这在 JavaScript 本身是做不到的,TypeScript 提供了
emitDecoratorMetadata
选项,让它成为可能。
1.2 元数据的作用
- 在框架设计中用于:依赖注入、路由注册、权限控制、序列化、验证等。
- 特别适合用于**装饰器(Decorator)**中,通过装饰器记录额外信息。
🧪 二、完整 API 说明
方法 | 描述 |
| 定义元数据 |
| 是否存在元数据 |
| 获取元数据 |
| 删除元数据 |
| 与 |
🛠️ 三、装饰器中常用的 TypeScript 自动注入类型元数据
当启用 emitDecoratorMetadata
,可以通过以下 metadata key 获取类型信息:
metadata key | 用途 |
| 属性类型 |
| 方法参数类型(数组) |
| 方法返回类型 |
📘 四、用法全案例详解
✅ 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 | 获取构造函数参数类型,自动实例化 |
权限控制 | 方法上标记元数据(如 |
路由注册 |
|
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';