😸
TypeScript で union をそれぞれ別の型に map する
type Base = "name" | "age"
type MapOperation<U> = U extends unknown ? {type: U} : never;
type MappedBase = MapOperation<Base>
// ^? type B = { type: "one" } | { type: "two" } | { type: "three" }
個人的にたまに使う型操作なのに、全然覚えられないシリーズの1つです。
TypeScript で map といえば Array.prototype.map
か Mapped Types
を思い浮かべる方が多いかもしれませんが、今回はこの2つは異なるものとなっています。整理のために、まずは上記の2つを紹介したいと思います。
// Array.prototype.map
const array = [1, 2, 3]
const mappedArray = array.map( => v * 2) // [2, 4, 6]
// Mapped Types
type Base = {
name: string
bio: string
}
type MappedType = {
[K in keyof Base]: K
} // { name: "name"; bio: "bio" }
概ねこんな感じだと思います。 今回やりたいのは、union 型の map です。実現したいことのイメージは以下の通りです。
type Base = "name" | "age"
type MappedBase = { name: "name" } | { age: "age" }
要は union の各要素に対してそれぞれの型操作を行い、新たな union を生成する操作を行いたいというわけです。
ちなみに以下の操作はうまくいきません。
type Base = "name" | "age"
type MappedBase = {
[K in Base]: K
}
この操作によって得られる MappedBase
は { name: "name"; age: "age" }
のように、キーがそれぞれある1つのオブジェクトの型を作り出してしまいます。
ここでのポイントは TypeScript で U extends X ? Y : Z
を評価する際に、 U が union である場合以下のような操作を行う特性を使う必要があるということです。
U = A | B の場合:
(U extends X ? Y : Z) => (A extends X ? Y : Z) | (B extends X ? Y : Z)
なので、少し手間ですが一つ型を追加して、以下のようにしてあげる必要があります。
type Base = "name" | "age"
type MapOperation<T> = T extends Base ? { [K in T]: K } : never;
type MappedBase = MapOperation<Base>
このようにすると、 T が "name" と "age" のそれぞれで { [K in T]: K }
を評価に、 union で結合されるようになります。
現在の TypeScript では generic type の generic type を作成することはできません。しかし、もしそれが可能になると今回実現したいことももっと簡単になるのになぁと思っています。 see: https://github.com/microsoft/TypeScript/issues/1213
Scala や Haskell なら実現できるんだろうなと思うとちょっといいなと思ったりします。
©︎ 2025 - Yard