アクセサに関する新しい仕組みとは
次に紹介するのは、アクセサに関する新しい仕組みです。
TypeScriptのアクセサ
アクセサは、JavaScriptのクラス構文にもある仕組みなので、当たり前のように使っている方も多いでしょう。本稿でも、プライベートアクセサの例として、#bmiゲッタをリスト4で紹介しています。#bmiはゲッタのみでしたが、例えば、#nameフィールドのアクセサとして、nameのゲッタとセッタの両方を定義するとなると、リスト8のようなコードになります。
class BMIData { #name: string; : get name(): string { // (1) return this.#name; } set name(value: string) { // (2) this.#name = value; } }
ここで注目するのは、リスト8の(1)と(2)の型指定です。プライベートな#bmiゲッタ同様に、TypeScriptらしく、ゲッタ、セッタの両方に型としてstringが指定されています。これにより、nameプロパティはstringとして働くようになります。
型指定に関する変更点
実は、同名のプロパティをセッタとゲッタの両方で定義する場合、それぞれの型として同じものを指定する必要がありました。この仕組みがバージョン4.3で変更になり、それぞれ別々の型指定が行えるようになりました。この仕組みを利用すると、例えば、リスト9のようなweightプロパティの定義が可能となります。
class BMIData { : #weight: number; : get weight(): string { // (1) return `体重${this.#weight}kg`; } set weight(value: number) { // (2) this.#weight = value; } }
リスト9では、(2)にあるように、weightプロパティのセッタはnumber型として定義しています。そのため、weightプロパティに値を代入する場合は、数値以外は受け付けません。一方で、(1)にあるように、weightプロパティにアクセスしてデータを取得した場合、「体重〇〇kg」という文字列表現が返ってくることになり、データ型はstringです。
実は、バージョン4.3でこの別々の型指定の仕組みが導入された時点では、リスト9のコードはエラーとなっていました。なぜなら、ゲッタのデータ型は必ずセッタに含まれている、つまり、セッタのデータ型のサブタイプである必要があったからです。そのため、リスト9のコードを実現するためには、セッタの定義は以下のようにstring型を含めておく必要がありました。
set weight(value: number | string) {
これが、バージョン5.1で変更になり、セッタとゲッタの型が完全に独立して指定できるようになりました。リスト9のコードが成立するのは、この変更のおかげです。
オートアクセサとは
アクセサ関連で本節の最後に紹介するのは、バージョン4.9で導入されたオートアクセサという機能です。これは、リスト10のように、フィールドにaccessorキーワードを付与することで、自動的にフィールドがプライベートになり、代わりに同名のアクセサが自動生成される仕組みです。
class BMIData { accessor name: string; : }
上記コードは、イメージ的にリスト11と同等の内容になります。
class BMIData { #name_accessor_storage: string; get name(): string { return this.#name_accessor_storage; } set name(value: string) { this.#name_accessor_storage = value; } }
このaccessorキーワードによるオートアクセサ機能は、ECMAScriptで提案されている機能であり、それをTypeScriptが先行導入したものです。ただし、原稿執筆時点では、ECMAScriptではまだステージ1の段階であり、まだまだ発展途上です。そのせいか、TypeScriptでもまだまだ不完全な状態です。例えば、readonlyキーワードが使えません。同じく、フィールドをプライベートにしてゲッタのみを定義することで、読み取り専用(readonly)プロパティが定義できますが、それもできません。ゲッタ、セッタそれぞれのカスタマイズもできません。本家のECMAScriptも含めて、この辺りの今後の発展に期待したいと思います。
staticブロックと
最後に紹介するのは、staticブロックです。これは、例えば、リスト12のようなコードです。
class Members { static count: number = 0; // (1) static { // (2) if(checkStatus()) { // (3) Members.count = getCurrentMemberCount(); // (4) } } }
リスト12の(2)がstaticブロックです。この仕組みは、ES2022で導入されたものであり、TypeScriptではバージョン4.4で先行導入されています。このstaticブロック内では、(1)のようなstatic変数の初期化処理が可能となります。
リスト12の初期化処理はひとつの例であり、(3)で関数checkStatus()を実行し、その結果に応じて、(4)のようにstatic変数であるcountの値をgetCurrentMemberCount()関数の戻り値の値とするような処理としています。
このように、ある条件に応じてstatic変数の値を変更するようなコードを記述しようとすると、コンストラクタではできません。なぜなら、コンストラクタはクラスがnewする際、すなわち、オブジェクトが生成される際に実行される処理であるため、オブジェクト横断で利用されるstatic変数の初期化処理はできないからです。これが、staticブロックが必要な理由です。
まとめ
TypeScriptのバージョン5.2までに導入された新機能をテーマごとに紹介する本連載の第5回目はいかがでしたでしょうか。
今回は、クラス構文に関する新しい仕組みを紹介しました。次回は、バージョン5.2までの新機能を紹介する最後のテーマとして「デコレータ」を紹介します。