TL;DR

  • 同じ型でも表示の仕方はいくつもあり、表示によって読みやすさが変わる。
  • Resolve 型を使うと、型の表示を分かりやすくしつつ実装の詳細を隠せる。

内容

型の表示は一通りではない

TypeScript には同じ型を表示する方法がいくつもあり、方法によって読みやすさが変わる。たとえばユニオン型のメンバーの並び順は、先に別の型を宣言しただけで変わることがある。

type T123 = "1" | "2" | "3" // '1' | '2' | '3'
type T21 = "2" | "1" // '2' | '1'
type T123 = "1" | "2" | "3" // '2' | '1' | '3'

Resolve 型

次の Resolve 型を使うと、型の表示を分かりやすくしつつ、実装の詳細をカプセル化できる。同じものが Simplify、NOP、NOOP、MergeInsertions という名前で使われることもある。

type Resolve<T> = T extends Function ? T : { [K in keyof T]: T[K] }

条件型の部分を無視すると、オブジェクト型を受け取って同じ型を返す定義が残る。

type ObjIdentity<T> = { [K in keyof T]: T[K] }

これは準同型なマップ型なので、プリミティブ型には手を加えない。

type S = ObjIdentity<string> // string
type U = ObjIdentity<"A" | "B" | "C">

ただし関数を渡すと {} になってしまうので、条件型で関数を除外している。

type F = ObjIdentity<(a: number) => boolean> // {}

使い方

keyof の結果や、型演算を重ねた型を展開して表示できる。

interface Color {
  r: number
  g: number
  b: number
  a: number
}
 
type Chan = keyof Color // keyof Color
type ChanInline = Resolve<keyof Color> // "r" | "g" | "b" | "a"
interface BlogComment {
  commentId: number
  title: string
  content: string
}
 
// 改善前
type PartiallyPartial<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>
type PartComment = PartiallyPartial<BlogComment, "title">
// Partial<Pick<BlogComment, "title">> & Omit<BlogComment, "title">
 
// 改善後
type PartiallyPartial<T, K extends keyof T> = Resolve<Partial<Pick<T, K>> & Omit<T, K>>
type PartComment = PartiallyPartial<BlogComment, "title">
// { title?: string | undefined; commentId: number; content: string; }

条件型でエッジケースを扱うと、表示をさらに改善できることもある。次の例では、K が never のときに元の型名をそのまま表示させている。

// 改善前
type FullComment = PartiallyPartial<BlogComment, never>
// BlogComment と同じだが展開される
// { title?: string | undefined; commentId: number; content: string; }
 
// 改善後
type PartiallyPartial<T extends object, K extends keyof T> = [K] extends [never]
  ? T
  : Resolve<Partial<Pick<T, K>> & Omit<T, K>>
type FullComment = PartiallyPartial<BlogComment, never> // BlogComment

クラスを渡すと大量のプロパティが表示されるので、避けたほうがよい。

その他の方法

keyof の結果をインライン表示するには Exclude<keyof T, never>、オブジェクト型をインライン表示するには unknown & T や {} & T を使う方法もある。ただし、どちらも Resolve で代替でき、Resolve のほうが安定して動く。

リグレッションを避けるため、ジェネリック型とその表示に対するテストを書くとよい(項目55 型のテストを書く)。

キーワード