2023年1月29日日曜日

TypeScriptで「任意のプロパティーを持てるが特定のプロパティーだけ型が違う」オブジェクトの型宣言をする方法

タイトル意味不明かもしれません。ちょっとうまい表現が見つかりませんでした。要するにこういうオブジェクトを作りたいということです。

  • プロパティーa, b, cはstring型
  • それ以外の任意のプロパティーはnumber型

コードで書くとこんな感じ。

// a, b, cはstring型
foo.a = "a";
foo.b = "b";
foo.c = "c";

// それ以外でも任意のプロパティーを使えるが、これらは全てnumber型
foo.abc = 123;
foo.xyz = 456;
foo.bar = 789;

TypeScriptを使うならanyとか使わずにできるだけ厳密に型定義をつけたいのでやってみました。

多分周回遅れのネタだと思いますが、ちょっと苦労したので同じように苦労している他の方の参考になれば。。。

いろいろ試してみた

素直に型定義

まずはこれで。Playgroundはこちら

type Foo = {
	a: string;
	b: string;
	c: string;
	[key: string]: number;
}

型定義の時点でエラーが出たのでダメでした。

交差型(intersection type)

a, b, cとそれ以外を別で定義して、&で連結。Playgroundはこちら

type Foo1 = {
	a: string;
	b: string;
	c: string;
}
type Foo2 = {
	[key: string]: number;
}
type Foo = Foo1 & Foo2;

型定義は問題ないけど、Playgroundでお分かりの通り変数の初期化でエラーが出ました。aFoo1のstring型とFoo2のnumber型の両方に含まれるので、そのあたりが原因でしょうか。

ただし、初期化ではエラーになったけど、その後のプロパティーへの個別代入では問題ないというちょっと謎な挙動です。

テンプレートリテラル型

続いては比較的最近導入されたテンプレートリテラル型。Playgoundはこちら

type Foo1 = {
	a: string;
	b: string;
	c: string;
}
type Foo2 = {
	[key in string]: number;
}
type Foo = Foo1 & Foo2;

これも交差型と同様の挙動でした。

結論

  • 変数宣言時の初期化はできない
  • でも、宣言後のプロパティー個別代入ならできた

初期化できないならイヤだなぁ・・・

ただし条件次第では。。。

上記の条件ではプロパティー名が重複するのが問題なので、「それ以外の任意のプロパティー」ではなくプロパティー名が重複しないように条件をつければできます。

たとえば、

  • プロパティーa, b, cはstring型
  • no_で始まり、数字が続くプロパティーはnumber型

のように。

これならテンプレートリテラル型で対応できます。

type Foo1 = {
	a: string;
	b: string;
	c: string;
}
type Foo2 = {
	[key in `no_${number}`]: number;
}
type Foo = Foo1 & Foo2;

Playgoundを見てわかる通り、初期化も個別代入もOK。

元々の目的は果たせなかったけど、この方が曖昧さがないので、こちらを使えるなら使いましょう。

0 件のコメント:

コメントを投稿