📝 引言
📌 场景/痛点
在 TypeScript 5.0 之前,使用装饰器(Decorator)需要开启 experimentalDecorators 实验性选项。这种旧语法不仅与最终 ECMAScript 标准不一致,而且存在运行时开销大、类型元数据支持混乱等问题。
很多新手在面对 target、propertyKey、descriptor 这三件套时,往往是一知半解,甚至只是照搬网上的代码。
在 2026 年,装饰器已经定稿(Stage 4/Finalized)。 我们是时候拥抱新的标准写法,利用 context 对象和 addInitializer 来编写更优雅的元编程代码了。
✨ 最终效果
掌握新标准装饰器后,你将能写出像 Angular 或 NestJS 那样优雅的声明式代码。

📖 内容概览
本文将带你重构你的元编程思维:
🛠️ 正文
1. 环境准备
在 TypeScript 5.9 中,使用标准装饰器无需开启 experimentalDecorators(实际上开启它反而会使用旧语法)。
tsconfig.json 配置:
{
"compilerOptions": {
"target": "ES2022",
// ✅ 确保使用新标准
"experimentalDecorators": false,
}
}
2. 核心概念:新的函数签名
旧版装饰器通常接收 3 个参数(target, key, descriptor),而新版标准装饰器只接收 2 个参数,且结构完全不同。
2.1 方法装饰器
新语法:(value, context) => { … }
- value: 当前方法本身的函数。
- context: 一个包含元数据的对象(kind, name, static, access, addInitializer)。
2.2 类装饰器
新语法:(value, context) => { … }
- value: 当前类本身。
3. 实战一:方法日志装饰器 (@Logged)
我们要实现一个装饰器,给方法加上自动日志打印功能。
// src/decorators/logged.ts
// ✅ 新标准写法:只接受 value 和 context
function Logged(value: Function, context: ClassMethodDecoratorContext) {
// context.kind 告诉我们这是否是方法
if (context.kind !== "method") return;
// 替换原方法
return function (this: any, …args: any[]) {
console.log(`[LOG] Calling method: ${String(context.name)}`);
console.log(`[LOG] Arguments: ${JSON.stringify(args)}`);
// 调用原方法
const result = value.apply(this, args);
console.log(`[LOG] Result: ${result}`);
return result;
};
}
// 使用
class Calculator {
@Logged
add(a: number, b: number) {
return a + b;
}
}
const calc = new Calculator();
calc.add(1, 2);
// 输出:
// [LOG] Calling method: add
// [LOG] Arguments: [1, 2]
// [LOG] Result: 3
4. 实战二:字段自动校验装饰器 (@Positive)
字段装饰器在标准版中有一个巨大的变化:它需要返回一个初始化函数。
// src/decorators/positive.ts
function Positive(value: undefined, context: ClassFieldDecoratorContext) {
if (context.kind !== "field") return;
// ✅ 返回一个函数,这个函数接收初始值
return function (initialValue: number) {
if (initialValue < 0) {
throw new Error(`${String(context.name)} must be positive!`);
}
return initialValue;
};
}
// 使用
class Product {
@Positive
price: number = 99; // ✅ 正常
@Positive
discount: number = –10; // ❌ 报错:discount must be positive!
}
new Product()
5. 实战三:初始化钩子 (addInitializer)
这是标准装饰器的一大亮点。你可以添加一个初始化函数,该函数会在类实例化(构造函数执行前)或类定义时运行。
function Init(value: undefined, context: ClassFieldDecoratorContext) {
if (context.kind !== "field") return;
// ✅ 添加初始化逻辑
context.addInitializer(function() {
// 这里的 this 是实例
// 我们可以在对象创建前做些手脚,比如给对象加个属性
(this as any).initialized = true;
});
}
class User {
@Init
initialized!: boolean;
}
const u = new User();
console.log(u.initialized); // true
❓ 常见问题
Q1: 旧版装饰器还能用吗?
A: 可以,但不推荐。只要开启 experimentalDecorators: true,旧代码依然能跑。但在 2026 年的新项目中,强烈建议使用标准装饰器,因为它运行时性能更好,且是 JS 官方标准。
Q2: 新标准里 Reflect.getMetadata 还能用来获取参数类型吗?
A: 这是一个痛点。标准的 ECMAScript 装饰器提案本身不包含类型元数据(Type Metadata)机制。 emitDecoratorMetadata 是 TS 的专有特性。目前(TS 5.9),要结合新装饰器使用类型注入(如 Angular/NestJS 的依赖注入),可能仍需一些变通或辅助库(如 reflect-metadata 的 Polyfill 或新的元数据提案)。纯业务开发(日志、校验)使用标准装饰器已完全足够。
Q3: 装饰器 @sealed 怎么写?
A: 对于类装饰器,你通常直接返回修改后的类,或者使用 addInitializer 封闭 prototype。
function sealed(value: Function, context: ClassDecoratorContext) {
Object.seal(value);
Object.seal(value.prototype);
}
🎯 总结
标准装饰器让 TypeScript 的元编程能力更加健壮和规范。本文我们掌握了:
🚀 下期预告: 掌握了前端和 Node.js 的 TS 开发,现在我们要挑战全栈。如何让后端的类型定义自动流向前端,彻底消除接口联调的烦恼? 下一篇文章我们将深入 《全栈类型共享:从 Prisma 到前端》,实现类型即真理。
💬 互动环节: 你的项目里还在用旧版的 experimentalDecorators 吗?有没有尝试迁移到新标准? 如果觉得文章带你紧跟了技术前沿,请点赞👍、收藏⭐、关注👀,下期更精彩!




