装饰器与元数据(Experimental)
注意:TypeScript 装饰器目前处于实验阶段。
从 TypeScript 5.0 起,已开始支持 ECMAScript 装饰器提案(Stage 3),与旧版 experimentalDecorators 不兼容。
本节基于 新版标准装饰器 讲解,同时说明与 reflect-metadata 的协作方式。
核心问题解析
问题一:类装饰器怎么修改类结构?
装饰器是一种特殊函数,用于拦截和修改类、方法、属性的定义。
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}@sealed 可以在运行时冻结类构造器或原型,防止修改。
答案:通过类装饰器函数,接收类的 constructor 或 classDescriptor,并可返回新定义。
问题二:如何在运行时读取类型信息?
TypeScript 的类型在编译后会被擦除:
class User {
name: string;
age: number;
}
// 编译后 JS 中没有 string 和 number 类型信息但我们希望在运行时知道某个参数是 string 或 User 类型。
答案:使用 emitDecoratorMetadata + reflect-metadata 在运行时存储和读取类型元数据。
问题三:reflect-metadata 怎么配合 TS 使用?
reflect-metadata 是一个 polyfill,提供了在对象上存储和读取元数据的 API。
结合 TS 的 emitDecoratorMetadata 选项,可以自动将类型信息写入元数据。
学习目标详解
目标一:理解实验性装饰器语法(experimentalDecorators)
1. 启用装饰器
在 tsconfig.json 中启用:
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}自 TS 5.0+,推荐使用标准装饰器(无需 experimentalDecorators),但目前生态仍广泛使用旧模式。
2. 装饰器类型
| 类型 | 用法 | 参数 |
|---|---|---|
| 类装饰器 | @decorator class C {} | (target: Function) => void | Function |
| 方法装饰器 | @decorator method() {} | (target, propertyKey, descriptor) |
| 属性装饰器 | @decorator prop: string; | (target, propertyKey) |
| 参数装饰器 | method(@decorator param: string) | (target, propertyKey, parameterIndex) |
目标二:实现类、方法、属性装饰器
1. 类装饰器:@logger
function logger<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
timestamp = Date.now();
};
}
@logger
class Person {
constructor(public name: string) {}
}
const p = new Person('Alice');
// p 现在有 timestamp 属性2. 方法装饰器:@readonly
function readonly(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.writable = false;
}
class MathLib {
@readonly
pi() {
return 3.14159;
}
}3. 属性装饰器:@observable
function observable(target: any, propertyKey: string) {
let value = target[propertyKey];
Object.defineProperty(target, propertyKey, {
get() { return value; },
set(newValue) {
console.log(`${propertyKey} changed to ${newValue}`);
value = newValue;
},
enumerable: true,
configurable: true
});
}
class Store {
@observable
count = 0;
}
const s = new Store();
s.count = 1; // 控制台输出:count changed to 14. 参数装饰器:@inject
function inject(service: string) {
return (target: any, propertyKey: string | undefined, parameterIndex: number) => {
console.log(`Inject ${service} into ${propertyKey} at index ${parameterIndex}`);
};
}
class UserService {
constructor(@inject('db') private db: any) {}
}目标三:结合 reflect-metadata 存储类型元数据
1. 安装依赖
npm install reflect-metadata在入口文件导入:
import 'reflect-metadata';2. 启用元数据 emit
确保 tsconfig.json 包含:
{
"emitDecoratorMetadata": true
}3. 读取类型元数据
class User {
id: number;
name: string;
createdAt: Date;
}
// TS 会自动为构造函数参数 emit 元数据
const types = Reflect.getMetadata('design:paramtypes', User);
// types: [Number, String, Date]
const returnType = Reflect.getMetadata('design:returntype', User.prototype, 'toString');
// 如果有 toString 方法,返回其返回类型
const propType = Reflect.getMetadata('design:type', User.prototype, 'name');
// propType: String4. 自定义元数据
function validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey);
console.log(`Method ${propertyKey} expects params of type:`, paramTypes);
}
class ApiService {
@validate
createUser(name: string, age: number) {
// ...
}
}
// 输出:Method createUser expects params of type: [String, Number]目标四:了解装饰器在 NestJS 等框架中的应用
1. NestJS:依赖注入 + 控制器路由
@Controller('users')
export class UsersController {
constructor(private readonly userService: UserService) {}
@Get() // 方法装饰器:注册 GET 路由
findAll(): User[] {
return this.userService.findAll();
}
@Post()
@UsePipes(ValidationPipe) // 方法装饰器:应用管道
create(@Body() userDto: CreateUserDto): User {
// @Body() 参数装饰器:从请求体提取数据
return this.userService.create(userDto);
}
}NestJS 利用:
@Controller:类装饰器,标记为控制器@Get、@Post:方法装饰器,注册路由@Body、@Param:参数装饰器,提取请求数据@Inject:属性/参数装饰器,实现依赖注入
2. TypeORM:实体映射
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column('datetime')
createdAt: Date;
}装饰器将类映射到数据库表结构。
总结:本节核心要点
| 技术 | 用途 | 关键配置 |
|---|---|---|
| 类装饰器 | 修改类定义 | @decorator class C |
| 方法/属性装饰器 | 拦截方法调用、属性访问 | @readonly method() |
reflect-metadata | 运行时读取类型信息 | import 'reflect-metadata' |
emitDecoratorMetadata | 自动 emit 类型元数据 | tsconfig.json 选项 |
| NestJS / TypeORM | 装饰器驱动的框架设计 | 依赖注入、AOP、ORM 映射 |
装饰器 = 元编程 + AOP(面向切面编程)
它让你能在不修改业务逻辑的前提下,添加日志、权限、验证、路由等横切关注点。
练习题
练习题 1:实现 @deprecated 装饰器
function deprecated(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.warn(`Method ${propertyKey} is deprecated.`);
return originalMethod.apply(this, args);
};
}
class Legacy {
@deprecated
oldMethod() {
return 'old';
}
}练习题 2:读取构造函数参数类型
class PaymentService {
constructor(
private logger: Logger,
private db: Database
) {}
}
const paramTypes = Reflect.getMetadata('design:paramtypes', PaymentService);
// [Logger, Database] ← 假设这些类已定义练习题 3:自定义元数据装饰器
function Role(role: string) {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
Reflect.defineMetadata('role', role, target, propertyKey);
};
}
function getRole(target: any, propertyKey: string) {
return Reflect.getMetadata('role', target, propertyKey);
}
class AdminController {
@Role('admin')
deleteUser() {}
}
getRole(AdminController.prototype, 'deleteUser'); // 'admin'练习题 4:属性装饰器 + 元数据
function MinLength(length: number) {
return (target: any, propertyKey: string) => {
Reflect.defineMetadata('minLength', length, target, propertyKey);
};
}
class CreateUserDto {
@MinLength(3)
username: string;
}
Reflect.getMetadata('minLength', CreateUserDto.prototype, 'username'); // 3练习题 5:NestJS 风格的 @Get
function Get(path: string) {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
// 在框架中会注册路由
console.log(`GET ${path} -> ${propertyKey}`);
};
}
class UserController {
@Get('/users')
getAll() {}
}
// 输出:GET /users -> getAll装饰器是 TypeScript 中最强大的元编程特性之一,尤其在构建框架时极为有用。
虽然目前仍处于实验阶段,但结合 reflect-metadata,它已经支撑起了 NestJS、TypeORM 等企业级框架的底层架构。
掌握它,你就能理解现代 TS 框架的设计哲学。