TC39JavaScriptTypeScript新語法Decorators

TC39 新語法深度解析(六):Decorators — 裝飾器語法正式標準化,不只是 TypeScript 專屬

整理 TC39 Decorators(Stage 3)的標準化 API:class、method、accessor、field、getter/setter 的裝飾方式,與 TypeScript 實驗性裝飾器的完整差異分析。

· 3 分鐘閱讀

這是 TC39 新語法深度解析系列的第六篇。TypeScript 的 experimentalDecorators 已經存在多年,但 TC39 的正式標準化帶來了完全不同的 API。這個區別很重要——即使用 TypeScript,也要知道兩者的差異。


為什麼 TC39 Decorators 和 TypeScript experimentalDecorators 不一樣

TypeScript 的實驗性裝飾器誕生於 2014 年,基於當時的 ES Decorators 提案(已被撤回)。TC39 在 2023 年重新設計了整個 API,形成了完全不同的語意:

維度TypeScript experimentalTC39 標準
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 完全重新設計
  • accessor keyword 是新亮點
  • 框架正在遷移中

這是 TC39 新語法深度解析系列的第六篇。下一篇:Object.groupBy — 內建分組,終於不需要 reduce 黑魔法。


延伸閱讀


本文基於 TC39 Decorators 提案(Stage 3)整理。