📋 目錄
這是 TC39 新語法深度解析系列的第六篇。TypeScript 的
experimentalDecorators已經存在多年,但 TC39 的正式標準化帶來了完全不同的 API。這個區別很重要——即使用 TypeScript,也要知道兩者的差異。
為什麼 TC39 Decorators 和 TypeScript experimentalDecorators 不一樣
TypeScript 的實驗性裝飾器誕生於 2014 年,基於當時的 ES Decorators 提案(已被撤回)。TC39 在 2023 年重新設計了整個 API,形成了完全不同的語意:
| 維度 | TypeScript experimental | TC39 標準 |
|---|---|---|
| API | 舊版(即將移除) | 新版(context object) |
| class decorator | (target) => new target() | 返回 class 或 void |
| 可觀察属性初始化 | 透過 defineProperty | 透過 accessor keyword |
| 框架支援 | Angular、MobX、NestJS | 正在遷移中 |
TC39 Decorators 的五個可裝飾目標
// 1. 類別(class)
@sealed
class Person {
// 2. 類別欄位(field)
@format('YYYY-MM-DD')
birthDate: string;
// 3. accessor(新的 accessor keyword)
@observable
accessor name: string;
// 4. 方法(method)
@logged
greet(@required name: string) {
return `Hello, ${name}`;
}
// 5. getter / setter
@readOnly
get id() { return this._id; }
}
accessor:自動封裝的屬性
accessor 是 TC39 新增的 modifier,它讓屬性自動包裝成帶有 getter/setter 的 accessor 物件:
class Counter {
accessor count = 0; // 自動相當於 private #count + getter/setter
increment() {
this.count++; // 使用時就像普通屬性
}
}
裝飾器在 accessor 上可以觀察每次的 get/set:
function tracked(target, context) {
if (context.kind === 'accessor') {
return class extends target {
get() {
console.log('get', context.name);
return super.get;
}
set(v) {
console.log('set', context.name, v);
super.set(v);
}
};
}
}
class Demo {
@tracked
accessor value = 0;
}
實用裝飾器模式
@logged:自動記錄方法呼叫
function logged(original, context) {
if (context.kind === 'method') {
return function replacement(...args) {
console.log(`Calling ${context.name} with`, args);
const result = original.call(this, ...args);
console.log(`${context.name} returned`, result);
return result;
};
}
}
class OrderService {
@logged
async placeOrder(itemId: string, quantity: number) {
// ...
return { orderId: '12345' };
}
}
@memoize:快取計算結果
function memoize(original, context) {
if (context.kind === 'method') {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = original.call(this, ...args);
cache.set(key, result);
return result;
};
}
}
class DataProcessor {
@memoize
heavyComputation(input: string): number {
return expensiveCalculation(input);
}
}
與 MobX / Angular 的遷移
MobX 和 Angular(舊版)都是基於 TypeScript experimentalDecorators。這兩個框架都正在遷移到 TC39 標準:
// Angular 19+ 新語法(正在遷移中)
// 舊(experimental):
@Injectable()
class MyService { }
// 新(TC39):
class MyService {
#inject = inject(HttpClient); // 使用 inject() 替代 decorator
}
瀏覽器支援
TC39 Decorators 目前 Stage 3,需要 Babel 或 TypeScript 5.0+(experimentalDecorators: false):
// tsconfig.json(TypeScript 5.0+)
{
"compilerOptions": {
"experimentalDecorators": false,
"useDefineForClassFields": false
}
}
# Babel 外掛
npm install --save-dev @babel/plugin-proposal-decorators
總結
TC39 Decorators 是十年標準化努力的成果。與 TypeScript 舊版 experimentalDecorators 的差異:
- API 完全重新設計
accessorkeyword 是新亮點- 框架正在遷移中
這是 TC39 新語法深度解析系列的第六篇。下一篇:Object.groupBy — 內建分組,終於不需要 reduce 黑魔法。
延伸閱讀
- TypeScript 6.0 RC 升級指南 — 支援最新 TC39 語法
- TypeScript Strict 模式指南 — 嚴格類型檢查
本文基於 TC39 Decorators 提案(Stage 3)整理。