🌟

TypeScript@5のDecoratorを動かしてみる

2023/01/31に公開

TypeScript5 と TC39/Stage3 への対応状態

検証バージョン

typescript@5 が beta に到達したので、typescript@5.0.0-betaでの検証を行いました。
検証に使用したテストプログラムは以下の場所に置いています。
https://github.com/SoraKumo001/typescript-decorator

使える Decorator 一覧

  • ClassDecorator
  • ClassMethodDecorator
  • ClassGetterDecorator
  • ClassSetterDecorator
  • ClassFieldDecorator
  • ClassAccessorDecorator

Parameter 関連の Decorator はありません。
また、DecoratorContext の access プロパティは pending になっています。

動作確認

テストプログラム

typescript@5.0.0-betaで動作するようにテストプログラムを書きました。
Decorator には標準対応しているので、tsconfig.jsonは標準のままで動作します。

検証用に受け取ったパラメータを表示しつつ、プロパティやメソッドの戻り値を文字列に変換するようにしています。

type AnyFunction = (...args: any[]) => any;

type S3ClassDecorator = (
  value: Function,
  context: ClassDecoratorContext
) => void | (new () => any);

type ClassMethodDecorator = (
  value: Function,
  context:
    | ClassMethodDecoratorContext
    | ClassGetterDecoratorContext
    | ClassSetterDecoratorContext
) => void | AnyFunction;

type ClassFieldDecorator = (
  value: undefined,
  context: ClassFieldDecoratorContext
) => (initialValue: unknown) => any | void;

type ClassAccessorDecorator = (
  value: ClassAccessorDecoratorTarget<unknown, unknown>,
  context: ClassAccessorDecoratorContext
) => ClassAccessorDecoratorResult<unknown, any>;

const classDecorator: S3ClassDecorator = (value, context) => {
  context.addInitializer(() =>
    console.log(`初期化:${context.kind}:${String(context.name)}`)
  );
  console.log(value, context);
};

const classFieldDecorator: ClassFieldDecorator = (value, context) => {
  console.log(value, context);
  return (initialValue) =>
    `取得:${context.kind}:${String(context.name)} => ${initialValue}`;
};

const classMethodDecorator: ClassMethodDecorator = (value, context) => {
  console.log(value, context);
  context.addInitializer(() =>
    console.log(`初期化:${context.kind}:${String(context.name)}`)
  );
  return function (this: unknown, ...args: any[]) {
    return `取得:${context.kind}:${String(context.name)} => ${value.apply(
      this,
      args
    )}`;
  };
};

const classAccessorDecorator: ClassAccessorDecorator = (value, context) => {
  console.log(value, context);
  return {
    get(this) {
      return `取得:${context.kind}:${String(context.name)} => ${value.get.apply(
        this
      )}`;
    },
  };
};

console.log("START");

@classDecorator
class Test1 {
  @classAccessorDecorator
  a = 1;
  @classFieldDecorator
  b = 10;
  @classMethodDecorator
  get c() {
    return 123;
  }
  @classMethodDecorator
  add(a: number, b: number) {
    return a + b;
  }
}

console.log("test1");
const test = new Test1();
console.log(test.a);
console.log(test.b);
console.log(test.c);
console.log(test.add(10, 20));

console.log("test2");
const test2 = new Test1();
console.log(test2.a);
console.log(test2.b);
console.log(test2.c);
console.log(test2.add(10, 20));

console.log("END");

出力結果

START
{ get: [Function: get a], set: [Function: set a] } {
  kind: 'accessor',
  name: 'a',
  static: false,
  private: false,
  addInitializer: [Function (anonymous)]
}
[Function: get c] {
  kind: 'getter',
  name: 'c',
  static: false,
  private: false,
  addInitializer: [Function (anonymous)]
}
[Function: add] {
  kind: 'method',
  name: 'add',
  static: false,
  private: false,
  addInitializer: [Function (anonymous)]
}
undefined {
  kind: 'field',
  name: 'b',
  static: false,
  private: false,
  addInitializer: [Function (anonymous)]
}
[class Test1] {
  kind: 'class',
  name: 'Test1',
  addInitializer: [Function (anonymous)]
}
初期化:class:Test1
test1
初期化:getter:c
初期化:method:add
取得:accessor:a => 1
取得:field:b => 10
取得:getter:c => 123
取得:method:add => 30
test2
初期化:getter:c
初期化:method:add
取得:accessor:a => 1
取得:field:b => 10
取得:getter:c => 123
取得:method:add => 30
END

まとめ

legacy の Decorator と比べると完全に構造が変わっており、古いプログラムをこちらに対応させようとすると、それなりに書き換えが必要です。また ParameterDecorator が現時点で存在していないので、それに依存したプログラムだと移植は難しいでしょう。なかなか使いどころが難しい感じになってしまいました。

GitHubで編集を提案

Discussion