在 TypeScript 中,元数据(Metadata) 是指附加到类、方法、属性或参数上的额外信息,这些信息可以在运行时被读取和使用。
元数据通常用于描述目标的结构、行为或其他特性,广泛应用于依赖注入、序列化、验证等场景。
TypeScript 通过 装饰器 和 反射元数据 API 来实现元数据的添加和读取。
1. 什么是元数据?
元数据是"关于数据的数据",它描述了目标(如类、方法、属性等)的额外信息。例如:
- 一个类的依赖项
- 一个方法的参数类型
- 一个属性的验证规则
在 TypeScript 中,元数据通常以键值对的形式存储,可以通过反射 API 在运行时访问。
2. 元数据的使用
TypeScript 提供了 reflect-metadata
库来支持元数据的操作。你需要先安装这个库:
js
npm install reflect-metadata
然后在代码中引入:
js
import "reflect-metadata";
2.1 添加元数据
使用 Reflect.defineMetadata
方法可以为目标添加元数据。它的语法如下:
js
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey?);
metadataKey
:元数据的键(通常是字符串或符号)。metadataValue
:元数据的值(可以是任意类型)。target
:目标对象(类、方法、属性等)。propertyKey
:可选参数,表示属性或方法的名称。
2.2 读取元数据
使用 Reflect.getMetadata
方法可以读取元数据。它的语法如下:
js
Reflect.getMetadata(metadataKey, target, propertyKey?);
metadataKey
:元数据的键。target
:目标对象。propertyKey
:可选参数,表示属性或方法的名称。
3. 为类、方法、属性和参数添加元数据
3.1 为类添加元数据
js
import "reflect-metadata";
# @Reflect.metadata是装饰器写法
@Reflect.metadata("classMeta", "This is a class metadata")
class MyClass {}
const classMeta = Reflect.getMetadata("classMeta", MyClass);
console.log(classMeta); // 输出: This is a class metadata
3.2 为方法添加元数据
js
import "reflect-metadata";
class MyClass {
@Reflect.metadata("methodMeta", "This is a method metadata")
myMethod() {}
}
const methodMeta = Reflect.getMetadata("methodMeta", MyClass.prototype, "myMethod");
console.log(methodMeta); // 输出: This is a method metadata
3.3 为属性添加元数据
js
import "reflect-metadata";
class MyClass {
@Reflect.metadata("propertyMeta", "This is a property metadata")
myProperty: string;
}
const propertyMeta = Reflect.getMetadata("propertyMeta", MyClass.prototype, "myProperty");
console.log(propertyMeta); // 输出: This is a property metadata
3.4 为参数添加元数据
js
import "reflect-metadata";
class MyClass {
myMethod(@Reflect.metadata("paramMeta", "This is a parameter metadata") arg1: string) {}
}
const paramMeta = Reflect.getMetadata("paramMeta", MyClass.prototype, "myMethod");
console.log(paramMeta); // 输出: { 0: "This is a parameter metadata" }
4. 元数据的应用场景
4.1 依赖注入
在 NestJS 等框架中,元数据用于实现依赖注入。例如:
js
import "reflect-metadata";
function Injectable() {
return function (target: any) {
Reflect.defineMetadata("injectable", true, target);
};
}
@Injectable()
class MyService {}
const isInjectable = Reflect.getMetadata("injectable", MyService);
console.log(isInjectable); // 输出: true
再来看一个例子:
js
# 在类的装饰器上面获取类方法的元数据
function controller(target: any) {
for (let key in target.prototype) {
console.log('key', Reflect.getMetadata('path', target.prototype, key))
}
}
# 通过方法装饰器给类的方法设置元数据,即path
function get(path: string) {
return function (target: any, key: string) {
Reflect.defineMetadata('path', path, target, key)
}
}
@controller
class LoginController {
constructor() {}
@get('/login')
login() {}
@get('/')
home(req: Request, res: Response) {
...
}
}
4.2 参数验证
可以使用元数据来存储参数的验证规则:
js
import "reflect-metadata";
function Validate(min: number, max: number) {
return function (target: any, propertyKey: string, parameterIndex: number) {
Reflect.defineMetadata("validation", { min, max }, target, `${propertyKey}_${parameterIndex}`);
};
}
class MyClass {
myMethod(@Validate(1, 10) arg1: number) {}
}
const validationRules = Reflect.getMetadata("validation", MyClass.prototype, "myMethod_0");
console.log(validationRules); // 输出: { min: 1, max: 10 }
4.3 序列化
序列化就是把程序中的数据结构(比如对象、数组等)转换为一种可以存储或者传输的格式的过程。这个过程就好像你要把一些物品(数据)装进一个箱子(特定格式)里,方便搬运(存储或传输)。在不同的应用场景中,序列化后的数据格式也有所不同,常见的有 JSON、XML、二进制等。
元数据可以用于描述如何序列化对象:
js
import "reflect-metadata";
function Serialize(key: string) {
return function (target: any, propertyKey: string) {
Reflect.defineMetadata("serialize", key, target, propertyKey);
};
}
class MyClass {
@Serialize("username")
name: string;
}
const serializationKey = Reflect.getMetadata("serialize", MyClass.prototype, "name");
console.log(serializationKey); // 输出: username
5. 内置元数据
design:type
design:type
是元数据的一种内置云数据,用于获取类成员(属性)的类型信息。
js
import 'reflect-metadata';
class Example {
@LogType
public name: string;
@LogType
public age: number;
}
function LogType(target: any, key: string) {
// 获取属性的类型
const type = Reflect.getMetadata('design:type', target, key);
console.log(`${key} 的类型是: ${type.name}`);
}
// 输出:
// name 的类型是: String
// age 的类型是: Number
design:paramtypes / design:returntype
design:paramtypes
获取方法参数的类型(数组形式); design:returntype
获取方法的返回值类型;
js
class Example {
@LogTypes
public greet(name: string, age: number): boolean {
return true;
}
}
function LogTypes(target: any, key: string, descriptor: PropertyDescriptor) {
const paramTypes = Reflect.getMetadata('design:paramtypes', target, key);
const returnType = Reflect.getMetadata('design:returntype', target, key);
console.log(`${key} 的参数类型是:`, paramTypes.map((t: any) => t.name).join(', '));
console.log(`${key} 的返回值类型是: ${returnType.name}`);
}
// 输出:
// greet 的参数类型是: String, Number
// greet 的返回值类型是: Boolean
6. 总结
- 元数据 是附加到类、方法、属性或参数上的额外信息,用于描述目标的结构或行为。
- TypeScript 通过
reflect-metadata
库支持元数据的操作。 - 使用
Reflect.defineMetadata
添加元数据,使用Reflect.getMetadata
读取元数据。 - 元数据的应用场景包括依赖注入、参数验证、序列化等。
通过元数据,你可以编写更灵活、更强大的代码,尤其是在框架开发中非常有用。