Metadata(元数据)是 TypeScript 中一个强大的特性,它允许你在运行时访问和操作与类、方法、属性等相关的附加信息。下面我将详细介绍 TypeScript 中的 Metadata。
什么是 Metadata
Metadata 是"关于数据的数据",在 TypeScript 中,它指的是附加到类、方法、属性或参数上的额外信息,这些信息可以在运行时被读取和使用。
启用 Metadata
要使用 Metadata,需要:
- 安装
reflect-metadata
库:
bash
npm install reflect-metadata
- 在
tsconfig.json
中启用相关配置:
json
json
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
}
- 在应用入口处导入
reflect-metadata
:
typescript
arduino
import "reflect-metadata";
基本用法
1. 定义 Metadata
typescript
less
// 定义类级别的元数据
@Reflect.metadata('classMeta', 'class metadata')
class MyClass {
// 定义属性级别的元数据
@Reflect.metadata('propMeta', 'property metadata')
myProp: string;
// 定义方法级别的元数据
@Reflect.metadata('methodMeta', 'method metadata')
myMethod() {}
// 定义参数级别的元数据
myMethod2(@Reflect.metadata('paramMeta', 'parameter metadata') param: string) {}
}
2. 获取 Metadata
typescript
javascript
// 获取类元数据
const classMeta = Reflect.getMetadata('classMeta', MyClass);
// 获取属性元数据
const propMeta = Reflect.getMetadata('propMeta', MyClass.prototype, 'myProp');
// 获取方法元数据
const methodMeta = Reflect.getMetadata('methodMeta', MyClass.prototype, 'myMethod');
// 获取参数元数据
const paramMeta = Reflect.getMetadata('paramMeta', MyClass.prototype, 'myMethod2');
内置 Metadata
当启用 emitDecoratorMetadata
时,TypeScript 会自动为装饰器添加一些内置元数据:
design:type
- 属性的类型design:paramtypes
- 方法参数的类型design:returntype
- 方法的返回类型
typescript
typescript
class Example {
@LogProperty
public name: string;
@LogMethod
public greet(@LogParameter prefix: string): string {
return `${prefix} ${this.name}`;
}
}
function LogProperty(target: any, propertyKey: string) {
const type = Reflect.getMetadata('design:type', target, propertyKey);
console.log(`${propertyKey} type: ${type.name}`); // 输出: name type: String
}
function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const returnType = Reflect.getMetadata('design:returntype', target, propertyKey);
const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey);
console.log(`${propertyKey} return type: ${returnType.name}`); // 输出: greet return type: String
console.log(`${propertyKey} param types: ${paramTypes.map(p => p.name).join(', ')}`); // 输出: greet param types: String
}
function LogParameter(target: any, propertyKey: string, parameterIndex: number) {
const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey);
console.log(`Parameter ${parameterIndex} of ${propertyKey} has type ${paramTypes[parameterIndex].name}`);
// 输出: Parameter 0 of greet has type String
}
实际应用场景
1. 依赖注入
typescript
typescript
class Service {
doSomething() {
console.log('Service doing something');
}
}
class Client {
constructor(private service: Service) {}
execute() {
this.service.doSomething();
}
}
// 依赖注入容器
class Container {
static instances = new Map<Function, any>();
static resolve<T>(target: { new (...args: any[]): T }): T {
// 获取目标类的参数类型
const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
// 递归解析依赖
const dependencies = paramTypes.map((dep: Function) => {
if (!this.instances.has(dep)) {
this.instances.set(dep, this.resolve(dep as any));
}
return this.instances.get(dep);
});
// 创建实例
return new target(...dependencies);
}
}
const client = Container.resolve(Client);
client.execute(); // 输出: Service doing something
2. 验证装饰器
typescript
typescript
function Validate(min: number, max: number) {
return function(target: any, propertyKey: string) {
Reflect.defineMetadata('validation', { min, max }, target, propertyKey);
}
}
function validateInstance(obj: any) {
for (const key of Object.keys(obj)) {
const meta = Reflect.getMetadata('validation', obj, key);
if (meta) {
const value = obj[key];
if (value < meta.min || value > meta.max) {
throw new Error(`Property ${key} must be between ${meta.min} and ${meta.max}`);
}
}
}
}
class User {
@Validate(1, 100)
age: number;
constructor(age: number) {
this.age = age;
validateInstance(this);
}
}
new User(50); // OK
new User(150); // 抛出错误
Metadata API 详解
主要方法
-
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey?)
- 定义元数据
-
Reflect.getMetadata(metadataKey, target, propertyKey?)
- 获取元数据
-
Reflect.hasMetadata(metadataKey, target, propertyKey?)
- 检查是否存在元数据
-
Reflect.getMetadataKeys(target, propertyKey?)
- 获取所有元数据键
-
Reflect.deleteMetadata(metadataKey, target, propertyKey?)
- 删除元数据
装饰器与 Metadata
装饰器是使用 Metadata 的主要方式:
typescript
typescript
// 类装饰器
function ClassDecorator(constructor: Function) {
Reflect.defineMetadata('classMeta', 'value', constructor);
}
// 属性装饰器
function PropertyDecorator(target: any, propertyKey: string) {
Reflect.defineMetadata('propMeta', 'value', target, propertyKey);
}
// 方法装饰器
function MethodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata('methodMeta', 'value', target, propertyKey);
}
// 参数装饰器
function ParameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
Reflect.defineMetadata('paramMeta', 'value', target, propertyKey);
}
注意事项
- 性能考虑:大量使用 Metadata 可能会影响性能,特别是在频繁实例化的类中。
- 浏览器支持 :需要
reflect-metadata
polyfill,某些旧浏览器可能需要额外的 polyfill。 - 类型擦除:TypeScript 的类型信息在编译后会消失,只有使用装饰器时才会保留部分类型信息。
- 设计决策:Metadata 主要用于框架开发,普通应用开发中应谨慎使用。
Metadata 是 TypeScript 中非常强大的特性,特别适合用于开发框架和库,如 Angular、NestJS 等都广泛使用了 Metadata 来实现依赖注入、验证等功能。