装饰器是即将推出的ECMAScript特性,允许我们以可重用的方式自定义类及其成员,是TypeScript 5.x的重点新特性[1]。以下从基础概念到实战应用分四步详解:
装饰器本质是包装函数,通过@装饰器名语法应用于类或其成员(方法、属性等),用于修改或增强目标行为。其核心特点是在类定义时执行,而非实例化时。
基础案例:@loggedMethod装饰器
// 简单的方法装饰器:打印方法调用日志
function loggedMethod(originalMethod: any, context: ClassMethodDecoratorContext) {
const methodName = String(context.name);
return function(this: any, ...args: any[]) {
console.log(`调用方法 ${methodName},参数:${args}`);
const result = originalMethod.call(this, ...args);
console.log(`方法 ${methodName} 返回:${result}`);
return result;
};
}
class UserService {
@loggedMethod // 应用装饰器
getUser(id: number) {
return { id, name: "TypeScript" };
}
}
// 类定义时装饰器已执行,实例化后调用方法触发日志
const service = new UserService();
service.getUser(1); // 输出:调用方法 getUser,参数:1;方法 getUser 返回:{id:1,name:"TypeScript"}
方法装饰器函数接收两个核心参数:
originalMethod:被装饰的原方法 context:上下文对象,包含name(方法名)、addInitializer(添加初始化逻辑)等属性带类型的方法装饰器
为确保类型安全,需使用泛型定义装饰器,明确this指向、参数及返回值类型:
function loggedMethod<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
const methodName = context.name;
return function(this: This, ...args: Args): Return {
console.log(`[${methodName}] 调用参数:`, args);
const result = target.call(this, ...args);
console.log(`[${methodName}] 返回结果:`, result);
return result;
};
}
此处This约束方法的this类型,Args和Return分别约束参数列表和返回值类型,使装饰器具备类型推导能力。
装饰器工厂是返回装饰器的函数,允许通过参数自定义装饰器逻辑。例如,为日志添加自定义前缀:
案例:带参数的loggedMethod工厂
function loggedMethod(headMessage = "LOG:") {
// 返回实际装饰器函数
return function<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
const methodName = context.name;
return function(this: This, ...args: Args): Return {
console.log(`${headMessage}[${methodName}] 调用参数:`, args);
const result = target.call(this, ...args);
console.log(`${headMessage}[${methodName}] 返回结果:`, result);
return result;
};
};
}
class UserService {
// 使用工厂自定义日志前缀
@loggedMethod("USER-LOG:")
getUser(id: number) {
return { id, name: "TypeScript" };
}
}
多个装饰器组合时,执行顺序为从下到上(靠近方法的装饰器先执行):
// 装饰器1:记录执行时间
function PerformanceMonitor(
target: Function,
context: ClassMethodDecoratorContext
) {
return function(this: any, ...args: any[]) {
const start = Date.now();
const result = target.call(this, ...args);
console.log(`执行时间:${Date.now() - start}ms`);
return result;
};
}
// 装饰器2:捕获错误
function ErrorCatch(
target: Function,
context: ClassMethodDecoratorContext
) {
return function(this: any, ...args: any[]) {
try {
return target.call(this, ...args);
} catch (error) {
console.log(`捕获错误:${error}`);
return null;
}
};
}
class DataService {
// 装饰器顺序:先执行 PerformanceMonitor,再执行 ErrorCatch
@ErrorCatch
@PerformanceMonitor
fetchData() {
if (Math.random() > 0.5) throw new Error("网络异常");
return "数据结果";
}
}
调用fetchData时,实际执行顺序为:PerformanceMonitor → 原方法 → ErrorCatch(若抛出错误)。
TypeScript 5.x装饰器基于ECMAScript提案,与旧版(需--experimentalDecorators)不兼容:
--emitDecoratorMetadata编译选项,无法自动生成元数据 target(类原型)、propertyKey、descriptor参数注意事项
--emitDecoratorMetadata,如需元数据需手动实现。性能监控+错误捕获装饰器组合
// 性能监控装饰器工厂(支持自定义日志前缀)
function PerformanceMonitor(prefix = "耗时:") {
return function<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
return function(this: This, ...args: Args): Return {
const start = Date.now();
const result = target.call(this, ...args);
console.log(`${prefix}${Date.now() - start}ms`);
return result;
};
};
}
// 错误捕获装饰器工厂(支持自定义错误消息)
function ErrorCatch(errorMessage = "操作失败") {
return function<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
return function(this: This, ...args: Args): Return | null {
try {
return target.call(this, ...args);
} catch (error) {
console.log(`${errorMessage}:${error}`);
return null;
}
};
};
}
// 组合使用
class TaskService {
@ErrorCatch("数据同步失败")
@PerformanceMonitor("同步耗时:")
syncData() {
// 模拟随机失败
if (Math.random() > 0.5) throw new Error("网络超时");
return "同步完成";
}
}
const task = new TaskService();
task.syncData(); // 成功:输出“同步耗时:Xms”;失败:输出“数据同步失败:网络超时”
@MaxLength需求:编写@MaxLength(limit)属性装饰器,限制类属性字符串长度,超出时抛出错误。
实现思路:
limit,返回属性装饰器。 context.addInitializer添加初始化逻辑,在属性赋值时校验长度。参考代码:
function MaxLength(limit: number) {
return function<This, Value extends string>(
target: undefined, // 属性装饰器的target为undefined(因属性未初始化)
context: ClassFieldDecoratorContext<This, Value>
) {
const fieldName = context.name;
// 添加初始化逻辑,拦截属性赋值
context.addInitializer(function (this: This) {
let value: Value;
Object.defineProperty(this, fieldName, {
get: () => value,
set: (newValue: Value) => {
if (newValue.length > limit) {
throw new Error(`属性 ${String(fieldName)} 长度超出限制(最大${limit})`);
}
value = newValue;
},
});
});
};
}
// 使用示例
class User {
@MaxLength(10)
username: string;
constructor(username: string) {
this.username = username; // 初始化时触发校验
}
}
new User("TypeScript5"); // 正常(长度9)
new User("TypeScript5.x"); // 抛出错误:属性 username 长度超出限制(最大10)
通过装饰器,我们可将类的增强逻辑(如日志、监控、校验)抽象为可重用组件,大幅提升代码复用性与可维护性。TypeScript 5.x对装饰器的支持使其成为类设计的强大工具,未来随着ECMAScript标准的稳定,其应用场景将更加广泛[1]。