TypeScript 装饰器是什么?常见使用场景有哪些?
简化版
装饰器是一种给类、方法、属性、参数附加元信息或包装行为的语法,常见于依赖注入、路由声明、ORM、日志和权限控制。TypeScript 装饰器依赖编译配置,不同装饰器提案阶段的语义也要区分。
详细版
常见写法:
@Controller('/users')
class UserController {
@Get('/:id')
findUser() {}
}
装饰器本质是函数。
它可以读取或修改被装饰目标的描述信息。
面试重点是:装饰器不是普通注释,它会参与编译和运行;使用前要确认 experimentalDecorators、元数据配置和框架语义。
完整版教学
一、装饰器解决声明式附加能力的问题
很多框架需要给类或方法附加额外信息。 例如这个类是控制器,这个方法对应 GET 路由,这个属性需要注入依赖。 如果不用装饰器,就要写大量配置对象或手工注册代码。
@Controller('/api')
class ApiController {}
装饰器让这些信息贴在目标代码旁边。 读者看到类声明,就能看到它在框架中的角色。 这是一种声明式元编程风格。
二、装饰器本质上是函数调用
装饰器语法看起来像注解,但它本质是函数。 类装饰器接收构造函数。 方法装饰器通常接收原型、方法名和属性描述符。
function Log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: unknown[]) {
console.log('call', key);
return original.apply(this, args);
};
}
class Service {
@Log
run() {}
}
上面例子里,方法调用前会多输出 1 条日志。 这说明装饰器可以包装行为,不只是记录元数据。
三、装饰器工厂可以接收参数
很多装饰器需要配置。 例如路由路径、权限名、缓存时间。 这时会写成装饰器工厂:外层函数接收配置,内层函数才是真正装饰器。
function Cache(ttl: number) {
return function (_target: any, key: string, descriptor: PropertyDescriptor) {
console.log(`cache ${key} for ${ttl}ms`);
};
}
class Repo {
@Cache(3000)
list() {}
}
流程如下:
读取 @Cache(3000)
-> 先调用 Cache 得到装饰器函数
-> 再把目标信息传给装饰器函数
这就是为什么 @Get('/users') 能传路由路径。
四、常见使用场景来自框架
装饰器在业务代码里不一定每天手写,但在框架里很常见。 NestJS 用它声明控制器、模块和依赖注入。 Angular 用它声明组件和服务。 ORM 用它声明实体字段和关系。
| 场景 | 装饰器作用 |
|---|---|
| 路由 | 标记 URL 和 HTTP 方法 |
| 依赖注入 | 标记可注入类和注入点 |
| ORM | 标记表、列、关系 |
| 日志权限 | 包装方法调用 |
| 校验 | 给字段附加校验规则 |
这些场景共同点是:需要把元信息绑定到类结构上。
五、reflect-metadata 和元数据
很多旧版 TypeScript 装饰器生态会配合 reflect-metadata。
它可以在运行时读取设计类型等元信息。
但类型信息能保留到什么程度有限,泛型等复杂类型通常会被擦除。
// 常见配置
// "experimentalDecorators": true
// "emitDecoratorMetadata": true
记忆钩子:装饰器能把元信息挂到代码结构上,但 TypeScript 类型不会完整搬到运行时。
如果面试追问“装饰器能不能拿到泛型 T”,要说明泛型编译后会擦除,不能指望运行时完整读取。
六、标准装饰器和旧装饰器要区分
JavaScript 装饰器标准经历过变化。 TypeScript 早期支持的是 experimental decorators,生态里很多框架基于旧语义。 较新的标准装饰器语义与旧版存在差异。
旧 TS experimental decorators
-> Angular / NestJS 等生态大量使用
新 ECMAScript decorators
-> 标准化方向,语义不同
工程里不要只看语法能不能写。 要看 TypeScript 版本、tsconfig、Babel 配置、框架要求是否一致。 装饰器一旦配置错,可能编译能过但运行时元数据缺失。
七、常见误区与追问
- 误区:装饰器只是注释。 装饰器是函数调用,可以记录元数据或修改行为。
- 误区:装饰器能拿到完整 TypeScript 类型。 TS 类型大多会擦除,运行时只能拿到有限元数据。
- 误区:所有装饰器语义都一样。 旧 TS experimental decorators 和标准装饰器语义有差异。
- 追问:装饰器工厂是什么? 返回装饰器函数的函数,用来给装饰器传配置参数。
- 追问:为什么 NestJS 大量使用装饰器? 它需要声明模块、控制器、路由、依赖注入等元信息。
- 追问:使用装饰器要看哪些配置? 至少关注
experimentalDecorators、emitDecoratorMetadata、TS/Babel 版本和框架要求。
八、加强记忆
装饰器按“函数、目标、元信息”来记:它是函数,作用在类或成员上,用来附加元信息或包装行为。面试中不要只说语法糖,要补配置、运行时元数据和新旧语义差异。