装饰器(TS 5.0+)
约 2354 字大约 8 分钟
布欧-Lewyon
2026-05-16
首页 › TypeScript › 类与面向对象 › 装饰器(TS 5.0+)
装饰器是一种特殊的声明,可以附加到类、方法、属性、访问器或参数上,在定义时修改它们的行为。TS 5.0 正式实现了 ES 装饰器标准(Stage 3),不再需要 experimentalDecorators 选项。
标准装饰器 vs 旧装饰器
TS 5.0 之前的装饰器(experimentalDecorators: true)基于 TC39 Stage 1 提案,与现在的标准差异很大:
| 对比维度 | 旧装饰器(experimental) | 标准装饰器(TS 5.0+) |
|---|---|---|
| 参数 | 依赖具体类型,签名混乱 | 统一的 (value, context) 签名 |
| this 类型 | 丢失,需手动处理 | 保留 this 类型 |
| 元数据 | emitDecoratorMetadata 不标准 | 标准 context.metadata |
| 性能 | 生成较多辅助代码 | 编译产出更精简 |
| 可组合性 | 装饰器间相互影响 | 更好的隔离 |
| 模块主题 | 基于 reflect-metadata | 原生元数据支持 |
新项目一律使用标准装饰器(TS 5.0+,target ≥ ES2022,
experimentalDecorators: false)。
装饰器签名统一模型
所有标准装饰器都遵循同一模式:
function decorator(value, context) {
// value: 被装饰的目标
// context: 上下文对象
}context 对象
context 对象包含以下属性(根据装饰器类型可能缺少某些字段):
interface ClassDecoratorContext {
kind: 'class';
name: string | undefined;
addInitializer(initializer: () => void): void;
// metadata 是提案特性(TS 5.2+)
metadata?: Record<symbol | string, unknown>;
}
interface MethodDecoratorContext {
kind: 'method';
name: string | symbol;
static: boolean;
private: boolean;
access: { get: () => unknown };
addInitializer(initializer: () => void): void;
metadata?: Record<symbol | string, unknown>;
}
interface PropertyDecoratorContext {
kind: 'property';
name: string | symbol;
static: boolean;
private: boolean;
access: { get: () => unknown; set: (value: unknown) => void };
addInitializer(initializer: () => void): void;
metadata?: Record<symbol | string, unknown>;
}
interface AccessorDecoratorContext {
kind: 'accessor';
name: string | symbol;
static: boolean;
private: boolean;
access: { get: () => unknown; set: (value: unknown) => void };
addInitializer(initializer: () => void): void;
metadata?: Record<symbol | string, unknown>;
}
interface ParameterDecoratorContext {
kind: 'parameter';
name: string | symbol;
static: boolean;
private: boolean;
index: number;
metadata?: Record<symbol | string, unknown>;
}配置
{
"compilerOptions": {
"experimentalDecorators": false, // TS 5.0+ 无需此选项
"target": "ES2022" // 标准装饰器需要 ≥ ES2022
}
}装饰器工厂(带参数)
大多数装饰器需要接受参数。装饰器工厂返回一个装饰器函数:
function log(options?: { prefix?: string }) {
// 返回实际的装饰器
return function(value: Function, context: ClassDecoratorContext) {
// ...
};
}
@log({ prefix: 'Service' })
class MyService {}各类装饰器详解
1. 类装饰器
类装饰器的 value 是类本身,可以替换或包装类:
function seal<T extends Function>(value: T, context: ClassDecoratorContext): T {
if (context.kind !== 'class') return value;
console.log(`类 ${context.name} 已调用 seal`);
return value;
}
function timestamp<T extends Function>(value: T, context: ClassDecoratorContext): T | void {
// 如果返回 void,表示不替换类,只做副操作
const original = value;
return class extends original {
createdAt = new Date();
constructor(...args: any[]) {
super(...args);
console.log(`${context.name} 实例创建于 ${this.createdAt}`);
}
};
}
@seal
@timestamp
class UserService {
constructor(public name: string) {}
}带参数的类装饰器
function table(tableName: string) {
return function<T extends new (...args: any[]) => {}>(value: T, context: ClassDecoratorContext): T {
// 返回子类添加元数据
return class extends value {
static tableName = tableName;
constructor(...args: any[]) {
super(...args);
}
};
};
}
@table('users')
class UserEntity {
constructor(public id: number, public name: string) {}
}
// UserEntity.tableName → "users"类装饰器与 DI(依赖注入)
const container = new Map<string, any>();
function injectable<T extends new (...args: any[]) => {}>(value: T, context: ClassDecoratorContext): T {
return class extends value {
constructor(...args: any[]) {
super(...args);
container.set(context.name!, this);
}
};
}
@injectable
class DataService {
fetch() { return [1, 2, 3]; }
}
// container.get('DataService') → DataService 实例2. 方法装饰器
方法装饰器接收原始方法,可以替换或包装它。必须返回一个新函数或 void:
// 计时装饰器
function logDuration(value: Function, context: MethodDecoratorContext) {
if (context.kind !== 'method') return value;
return function(this: any, ...args: any[]) {
const start = performance.now();
const result = value.apply(this, args);
const duration = performance.now() - start;
console.log(`${String(context.name)} 耗时: ${duration.toFixed(2)}ms`);
return result;
};
}
// 权限控制装饰器
function requireRole(role: string) {
return function(value: Function, context: MethodDecoratorContext) {
return function(this: any, ...args: any[]) {
// 假设 this.userRole 存在
if ((this as any).userRole !== role && (this as any).userRole !== 'admin') {
throw new Error(`需要 ${role} 角色`);
}
return value.apply(this, args);
};
};
}
// 防抖装饰器
function debounce(delay: number = 300) {
let timer: ReturnType<typeof setTimeout>;
return function(value: Function, context: MethodDecoratorContext) {
return function(this: any, ...args: any[]) {
clearTimeout(timer);
timer = setTimeout(() => value.apply(this, args), delay);
};
};
}
class UserController {
userRole: string = 'editor';
@logDuration
fetchUsers() {
for (let i = 0; i < 1000000; i++) {}
return ['张三', '李四'];
}
@requireRole('admin')
deleteUser(id: number) {
console.log(`删除用户 ${id}`);
}
@debounce(500)
search(query: string) {
console.log(`搜索: ${query}`);
}
}重试装饰器
function retry(times: number = 3, delay: number = 1000) {
return function(value: Function, context: MethodDecoratorContext) {
return async function(this: any, ...args: any[]) {
let lastError: any;
for (let i = 0; i < times; i++) {
try {
return await value.apply(this, args);
} catch (err) {
lastError = err;
console.warn(`第 ${i + 1} 次尝试失败,${delay}ms 后重试`);
await new Promise(r => setTimeout(r, delay));
}
}
throw lastError;
};
};
}
class ApiService {
@retry(3, 500)
async fetchData() {
const res = await fetch('/api/data');
if (!res.ok) throw new Error('请求失败');
return res.json();
}
}3. 访问器装饰器(getter/setter)
TS 5.0 标准装饰器对 get / set 统一使用 @accessor 关键字:
// 注意:标准装饰器在 getter/setter 上使用时,value 对应的是 getter 函数
// context.kind 为 'accessor'
function lazy(value: any, context: AccessorDecoratorContext) {
if (context.kind !== 'accessor') return value;
const { get } = context.access;
return {
get() {
// 第一次访问后缓存结果
const result = get.call(this);
Object.defineProperty(this, context.name, {
value: result,
writable: false,
configurable: false,
});
return result;
},
set(value: any) {
// 不能通过 set 修改 lazy 属性
},
init(value: any) {
// init 在构造时初始化
return value;
}
};
}
class HeavyConfig {
@lazy
get config() {
// 只在第一次访问时执行
console.log('加载配置...');
return JSON.parse(localStorage.getItem('config') || '{}');
}
}4. 属性装饰器
属性装饰器不能修改属性本身(JS 没有属性钩子),但可以通过 addInitializer 做副操作:
function required(value: undefined, context: PropertyDecoratorContext) {
context.addInitializer(function(this: any) {
// 包装 setter 来校验
const key = context.name;
let val = this[key];
Object.defineProperty(this, key, {
get() { return val; },
set(newVal: any) {
if (newVal === null || newVal === undefined) {
throw new Error(`${String(key)} 是必填字段`);
}
val = newVal;
},
configurable: true,
enumerable: true,
});
});
}
// 绑定 this
function bind(value: undefined, context: PropertyDecoratorContext) {
context.addInitializer(function(this: any) {
// 将方法绑定到实例
const method = this[context.name];
if (typeof method === 'function') {
this[context.name] = method.bind(this);
}
});
}
class User {
@required
name!: string;
@bind
greet() {
console.log(`你好,${this.name}`);
}
}5. 参数装饰器
参数装饰器接收的参数签名不同——value 始终为 undefined,需要额外参数:
function validate(validator: (value: any) => boolean) {
return function(value: undefined, context: ParameterDecoratorContext) {
context.addInitializer(function(this: any) {
const original = this[context.name as keyof this] as Function;
if (!original) return;
(this as any)[context.name] = function(...args: any[]) {
// 在调用时校验指定参数
if (!validator(args[context.index])) {
throw new Error(`参数 #${context.index} 校验失败`);
}
return original.apply(this, args);
};
});
};
}
function isPositive(value: any): boolean {
return typeof value === 'number' && value > 0;
}
class MathService {
divide(a: number, @validate(isPositive) b: number) {
return a / b;
}
}addInitializer
addInitializer 是所有装饰器都支持的钩子,用于在构造时执行初始化代码:
function logInit(value: Function, context: MethodDecoratorContext) {
context.addInitializer(function() {
console.log(`方法 ${String(context.name)} 已初始化`);
});
return value;
}
class MyService {
@logInit
doWork() {}
}执行时机:
- 类装饰器:类定义完成后(非实例化时)
- 成员装饰器:实例化时(每个实例执行一次)
元数据(TS 5.2+)
TS 5.2+ 支持通过 context.metadata 访问装饰器元数据:
const META_KEY = Symbol('meta');
function setRole(role: string) {
return function(value: Function, context: MethodDecoratorContext) {
context.metadata[META_KEY] = role;
return value;
};
}
class AdminService {
@setRole('admin')
deleteUser() {}
}
// 读取元数据(需要通过装饰器链获取)
// 装饰器元数据是提案阶段,未来 API 可能变化装饰器组合
多个装饰器按从下到上的顺序执行(先应用离方法最近的):
class Service {
@logDuration
@retry(2)
@requireRole('admin')
async deleteUser(id: number) {
// 调用顺序:requireRole → retry → logDuration
// 日志在外层,重试在中间,权限在内层
}
}新旧装饰器对比与迁移
| 场景 | 旧装饰器(TS < 5.0) | 标准装饰器(TS 5.0+) |
|---|---|---|
| 签名 | (target, key, descriptor) | (value, context) |
| 方法替换 | 修改 descriptor.value | 返回新函数 |
| 属性初始化 | 修改 descriptor.initializer | 在 get 中返回 init 属性 |
| 元数据 | Reflect.metadata / reflect-metadata | context.metadata |
| 参数装饰器 | 可修改构造逻辑 | 不能直接修改,通过 addInitializer |
| 类型安全 | 大量 any | 根据 context.kind 推导 |
迁移示例
// 旧装饰器
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`调用 ${key}`);
return original.apply(this, args);
};
}
// → 标准装饰器
function log(value: Function, context: MethodDecoratorContext) {
return function(this: any, ...args: any[]) {
console.log(`调用 ${String(context.name)}`);
return value.apply(this, args);
};
}综合示例:完整的验证系统
function required(value: undefined, context: PropertyDecoratorContext) {
context.addInitializer(function(this: any) {
const key = context.name;
let val: any;
Object.defineProperty(this, key, {
get() { return val; },
set(newVal) {
if (newVal === null || newVal === undefined) {
throw new Error(`${String(key)} 不能为空`);
}
val = newVal;
},
enumerable: true,
configurable: true,
});
});
}
function minLength(min: number) {
return function(value: undefined, context: PropertyDecoratorContext) {
context.addInitializer(function(this: any) {
const key = context.name;
let val: string = '';
Object.defineProperty(this, key, {
get() { return val; },
set(newVal: string) {
if (typeof newVal === 'string' && newVal.length < min) {
throw new Error(`${String(key)} 最少 ${min} 个字符`);
}
val = newVal;
},
enumerable: true,
configurable: true,
});
});
};
}
function range(min: number, max: number) {
return function(value: undefined, context: PropertyDecoratorContext) {
context.addInitializer(function(this: any) {
const key = context.name;
let val: number = 0;
Object.defineProperty(this, key, {
get() { return val; },
set(newVal: number) {
if (typeof newVal === 'number' && (newVal < min || newVal > max)) {
throw new Error(`${String(key)} 必须在 ${min}~${max} 之间`);
}
val = newVal;
},
enumerable: true,
configurable: true,
});
});
};
}
class CreateUserRequest {
@required
@minLength(2)
name!: string;
@required
email!: string;
@range(18, 120)
age!: number;
}
// 使用
const req = new CreateUserRequest();
req.name = '张三'; // ✅
req.email = 'z@test.com'; // ✅
req.age = 28; // ✅
// req.age = 200; // ❌ 抛出错误最佳实践
- 新项目一律使用标准装饰器,不使用
experimentalDecorators - 装饰器工厂用于需要参数的场景,返回闭包
- 方法装饰器返回新函数来包装原始逻辑
- 属性装饰器通过
addInitializer做副操作,不能替换属性本身 - 类型安全:在装饰器内部根据
context.kind做类型守卫来判断处理的装饰器类型 - 避免过度使用:装饰器本质上是 AOP,滥用会导致代码隐式行为过多
小结
| 装饰器类型 | value | context.kind | 可以替换 |
|---|---|---|---|
| 类 | 类本身 | 'class' | ✅ 返回新类 |
| 方法 | 函数 | 'method' | ✅ 返回新函数 |
| 访问器 | getter | 'accessor' | ✅ 返回 { get, set, init } |
| 属性 | undefined | 'property' | ❌ 通过 addInitializer |
| 参数 | undefined | 'parameter' | ❌ 通过 addInitializer |
| 核心概念 | 说明 |
|---|---|
| 签名 | (value, context) 统一模型 |
| context | kind / name / access / addInitializer / metadata |
| 工厂 | 返回装饰器的函数,用于传参 |
| 组合 | 离目标近的先执行 |
| addInitializer | 实例化时执行的初始化钩子 |
上一节:抽象类与 this 类型 下一节:条件类型与 infer
