空雲 Blog

Eye catchTypeScript@5のDecoratorを動かしてみる

publication: 2023/01/31
update:2024/02/20

TypeScript5 と TC39/Stage3 への対応状態

検証バージョン

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は標準のままで動作します。

1type AnyFunction = (...args: any[]) => any;
2
3type S3ClassDecorator = (
4 value: Function,
5 context: ClassDecoratorContext
6) => void | (new () => any);
7
8type ClassMethodDecorator = (
9 value: Function,
10 context:
11 | ClassMethodDecoratorContext
12 | ClassGetterDecoratorContext
13 | ClassSetterDecoratorContext
14) => void | AnyFunction;
15
16type ClassFieldDecorator = (
17 value: undefined,
18 context: ClassFieldDecoratorContext
19) => (initialValue: unknown) => any | void;
20
21type ClassAccessorDecorator = (
22 value: ClassAccessorDecoratorTarget<unknown, unknown>,
23 context: ClassAccessorDecoratorContext
24) => ClassAccessorDecoratorResult<unknown, any>;
25
26const classDecorator: S3ClassDecorator = (value, context) => {
27 context.addInitializer(() =>
28 console.log(`初期化:${context.kind}:${String(context.name)}`)
29 );
30 console.log(value, context);
31};
32
33const classFieldDecorator: ClassFieldDecorator = (value, context) => {
34 console.log(value, context);
35 return (initialValue) =>
36 `取得:${context.kind}:${String(context.name)} => ${initialValue}`;
37};
38
39const classMethodDecorator: ClassMethodDecorator = (value, context) => {
40 console.log(value, context);
41 context.addInitializer(() =>
42 console.log(`初期化:${context.kind}:${String(context.name)}`)
43 );
44 return function (this: unknown, ...args: any[]) {
45 return `取得:${context.kind}:${String(context.name)} => ${value.apply(
46 this,
47 args
48 )}`;
49 };
50};
51
52const classAccessorDecorator: ClassAccessorDecorator = (value, context) => {
53 console.log(value, context);
54 return {
55 get(this) {
56 return `取得:${context.kind}:${String(context.name)} => ${value.get.apply(
57 this
58 )}`;
59 },
60 };
61};
62
63console.log("START");
64
65@classDecorator
66class Test1 {
67 @classAccessorDecorator
68 a = 1;
69 @classFieldDecorator
70 b = 10;
71 @classMethodDecorator
72 get c() {
73 return 123;
74 }
75 @classMethodDecorator
76 add(a: number, b: number) {
77 return a + b;
78 }
79}
80
81console.log("test1");
82const test = new Test1();
83console.log(test.a);
84console.log(test.b);
85console.log(test.c);
86console.log(test.add(10, 20));
87
88console.log("test2");
89const test2 = new Test1();
90console.log(test2.a);
91console.log(test2.b);
92console.log(test2.c);
93console.log(test2.add(10, 20));
94
95console.log("END");

出力結果

1START
2{ get: [Function: get a], set: [Function: set a] } {
3 kind: 'accessor',
4 name: 'a',
5 static: false,
6 private: false,
7 addInitializer: [Function (anonymous)]
8}
9[Function: get c] {
10 kind: 'getter',
11 name: 'c',
12 static: false,
13 private: false,
14 addInitializer: [Function (anonymous)]
15}
16[Function: add] {
17 kind: 'method',
18 name: 'add',
19 static: false,
20 private: false,
21 addInitializer: [Function (anonymous)]
22}
23undefined {
24 kind: 'field',
25 name: 'b',
26 static: false,
27 private: false,
28 addInitializer: [Function (anonymous)]
29}
30[class Test1] {
31 kind: 'class',
32 name: 'Test1',
33 addInitializer: [Function (anonymous)]
34}
35初期化:class:Test1
36test1
37初期化:getter:c
38初期化:method:add
39取得:accessor:a => 1
40取得:field:b => 10
41取得:getter:c => 123
42取得:method:add => 30
43test2
44初期化:getter:c
45初期化:method:add
46取得:accessor:a => 1
47取得:field:b => 10
48取得:getter:c => 123
49取得:method:add => 30
50END

まとめ

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