Appearance
类装饰器
1. 装饰器的本质
在 JavaScript 中,装饰器的本质是一个函数。
虽然装饰器的作用是提供元数据(Metadata),但它并非简单的静态数据声明,而是会参与到程序的运行过程中。装饰器可以修饰以下内容:
- 类(Class)
- 成员(属性 + 方法)
- 参数
2. 环境准备:tsconfig 设置
由于装饰器目前在 JavaScript 中仍处于 Stage 3 阶段(尚未正式成为最终规范),在 TypeScript 中使用装饰器需要开启实验性支持。
配置说明
在 tsconfig.json 中,必须将 experimentalDecorators 设置为 true:
json
{
"compilerOptions": {
"experimentalDecorators": true
}
}3. 类装饰器基础
类装饰器 是紧贴在类声明之前的函数。它只有一个参数,即 类本身(构造函数)。
3.1 构造函数的类型表示
在 TypeScript 中,我们通常有两种方式来描述一个构造函数:
Function:过于宽泛,不推荐。new (...args: any[]) => any:推荐做法,明确表示这是一个可以被new的构造函数。
typescript
// 定义一个简单的类装饰器
function classDecoration(target: new (...args: any[]) => any) {
console.log('装饰器执行了,目标类:', target);
}
@classDecoration
class A {}3.2 执行时机
重要特性
装饰器是在类定义时执行的,而不是在类实例化(new)时执行。
通过查看编译后的 JavaScript 代码,可以看到装饰器实际上是被 __decorate 工具函数包裹并立即执行的:
javascript
// 编译后的关键逻辑
let A = class A {};
A = __decorate([classDecoration], A); // 定义完类后立即执行装饰4. 进阶用法
4.1 泛型约束与类型别名
为了提高代码的可读性和复用性,我们通常会定义一个 Constructor 类型别名,并使用泛型进行约束。
typescript
// 定义通用的构造函数类型别名
type Constructor<T = any> = new (...args: any[]) => T;
interface User {
id: number;
name: string;
info(): void;
}
// 约束:该装饰器只能用于满足 User 接口定义的类
function validateUserClass<T extends Constructor<User>>(target: T) {
console.log('正在校验 User 类:', target.name);
}
@validateUserClass
class Admin {
constructor(
public id: number,
public name: string,
) {}
info() {
console.log('Admin info');
}
}4.2 装饰器工厂模式
如果我们需要向装饰器传递额外的参数,可以使用 工厂模式。装饰器工厂是一个简单的函数,它返回实际的装饰器函数。
typescript
function Logger(prefix: string) {
// 这才是真正的装饰器函数
return function (target: Function) {
console.log(`[${prefix}] 装饰了类: ${target.name}`);
};
}
@Logger('API_MODULE')
class UserService {}4.3 通过装饰器扩展类
装饰器甚至可以返回一个新的类来替换原有的类,从而实现属性注入或方法重写。
typescript
type Constructor = new (...args: any[]) => any;
function withTimestamps<T extends Constructor>(target: T) {
return class extends target {
createdAt = new Date();
printLog() {
console.log('日志记录时间:', this.createdAt);
}
};
}
@withTimestamps
class Document {
constructor(public title: string) {}
}
const doc = new Document('学习笔记');
console.log((doc as any).createdAt); // 输出当前时间类型提示问题
虽然装饰器可以动态修改类的行为,但 TypeScript 的类型系统目前无法直接感知装饰器返回的新属性。在访问新属性时,可能需要使用 as any 或者通过接口进行类型扩展。
5. 多装饰器执行顺序
当一个类应用了多个装饰器时,它们的执行逻辑遵循以下规律:
- 从上到下:依次执行装饰器工厂函数(如果有)。
- 从下到上:依次执行真正的装饰器函数(由近及远)。
typescript
function Dec(id: number) {
console.log(`工厂 ${id} 执行`);
return function (target: Function) {
console.log(`装饰器 ${id} 执行`);
};
}
@Dec(1)
@Dec(2)
class Test {}控制台输出:
text
工厂 1 执行
工厂 2 执行
装饰器 2 执行
装饰器 1 执行6. 总结
- 本质:装饰器是运行时的函数,而非静态类型。
- 时机:类定义时即触发,非实例化时。
- 能力:可以观察、修改甚至替换被装饰的类。
- 顺序:工厂自上而下,装饰器自下而上。
