编程全栈开发者

TypeScript中的类型装饰器(Decorator)是如何工作的?它们用于什么,如何实现自定义装饰器,以及在使用时会遇到哪些陷阱?

用 Hintsage AI 助手通过面试

答案。

装饰器是特殊的注解,它们允许通过额外的元信息或替代实现来改变类、属性、方法或参数的行为。

通常用于依赖注入、日志记录、验证、缓存或为像Angular之类的框架声明元数据。

简单方法装饰器示例:

function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function(...args: any[]) { console.log(`Call: ${propertyKey}(${JSON.stringify(args)})`); return original.apply(this, args); }; } class Example { @Log doSomething(a: number, b: number) { return a + b; } }

陷阱:

  • 装饰器只有在tsconfig中启用experimentalDecorators时才有效。
  • 与某些严格模式设置不兼容。
  • 装饰器的调用顺序并不总是如预期那样;理解调用栈是很重要的。
  • 参数装饰器不允许直接改变参数的值。

陷阱问题。

问题: "是否可以对装饰器的参数进行类型化,以确保应用它的方法的类型?"

答案: 在大多数情况下,由于元编程的动态特性,装饰器应用时的类型化是不可能的。然而,通过泛型和额外的运行时检查可以添加验证,但TypeScript编译器在声明结构时无法提供保证。

示例:

function MyDecorator(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<number>) { /* ... */ } // 编译器并不保证该方法返回number

由于对主题细微差别的无知而导致的实际错误示例。


故事

在一个有大量服务装饰器的项目中,忘记了应用顺序:属性装饰器在所有参数之前被应用,这导致Inject元数据丢失,并且在运行时无法解析依赖关系。


故事

开发者决定将装饰器应用于普通函数,而不是类的方法,并对编译错误和缺乏运行时效果感到惊讶。在TypeScript规格中,装饰器仅适用于类及其成员。


故事

在更新TypeScript版本和实验性设置后,自定义DI容器中的装饰器机制完全崩溃:新版本显然需要显式支持Reflect-metadata,否则所有元信息都会丢失。