やりたいこと
特定の型のオブジェクトが格納されている配列から、そのオブジェクトの特定のキーとそのキー自身をバリューとするオブジェクトを型定義したかった。
例えば次のような配列から
const objects = {
fruits: [
{ key: 'apple', value: 'りんご' },
{ key: 'orange', value: 'オレンジ' },
{ key: 'grape', value: 'ぶどう' },
],
animals: [
{ key: 'dog', value: '犬' },
{ key: 'cat', value: '猫' },
{ key: 'bird', value: '鳥' },
],
}
次のような型定義をしたい。
type Fruits = {
apple: "apple";
orange: "orange";
grape: "grape";
}
type Animals = {
dog: "dog";
cat: "cat";
bird: "bird";
}
今回は各オブジェクトから期待する型を生成できるObjectKey型の作成を目標とした。
type Fruits = ObjectKey<typeof objects.fruits>
type Animals = ObjectKey<typeof objects.animals>
型アサーション
今回は"dog"
や"cat"
のような特定の値を定義したいため、型アサーションを利用する。
const objects = {
fruits: [
{ key: 'apple', value: 'りんご' },
{ key: 'orange', value: 'オレンジ' },
{ key: 'grape', value: 'ぶどう' },
],
animals: [
{ key: 'dog', value: '犬' },
{ key: 'cat', value: '猫' },
{ key: 'bird', value: '鳥' },
],
} as const
一応できた
mapped typesを利用して一応、期待する型を表現することはできた。
type ObjectKey<T extends string> = {
[K in T]: K
}
type Fruits = ObjectKey<typeof objects.fruits[number]['key']>
// type Fruits = {
// apple: "apple";
// orange: "orange";
// grape: "grape";
// }
しかし、これだと具体的な型を定義する場合に、冗長な記述が必要になる。
またシンプルなstring型を渡すこともできるので、想定外の使い方ができてしまう。
ちなみに文字列を渡すと以下のようになる
type ObjectKey<T extends string> = {
[K in T]: K
}
type Hoge = ObjectKey<'hoge'>
// type Hoge = {
// hoge: "hoge";
// }
これはこれで使い道があるかもしれないが...
もう少し厳密にしてみた
type ObjectKey<T extends Readonly<Array<{ key: string; value: string }>>> = {
[K in T[number]['key']]: K
}
type Fruits = ObjectKey<typeof objects.fruits>
type Animals = ObjectKey<typeof objects.animals>
Tには特定の型を持つオブジェクトを格納した配列しか入れませんという誓約をし、Tの制約を強くすることで型の厳密さがあがった(制約と誓約)。
また、型ObjectKeyから型を生成するときの記述量も減らすことができた。
個人的にはtypeofの記述もなくしたいところだが、今の自分ではやり方がわからなかったので誰か教えて欲しいところ。
満点とは言えないが個人的に満足な型を作ることができた。
まとめ
TypeScriptで作りたい型を作る過程が最近楽しい。
使い道がわからない機能や使ったことがない機能がたくさんあるので、うまく使いこなせるようになりたい。
参考