TL;DR
内容
型と DRY
DRY は型にも当てはまる。ただし、やりすぎには注意する(DRY の守破離、「重複は間違った抽象化よりはるかに無害である」)。プロパティや型を共有するときは、それらが本当に共通のものかを確かめる。次の例は、Product と Customer がたまたま id と name を持っているだけで、共通化する意味がない。
// ダメな例
interface NameAndIdentified {
id: number
name: string
}
interface Product extends NameAndIdentified {
priceDollars: number
}
interface Customer extends NameAndIdentified {
address: string
}著者の経験則では、共通化した型に名前を付けにくいときは、共通化に意味がないことが多い。
型の重複を避ける方法
同じ型を繰り返し書く代わりに、その型に名前を付ける。interface 間でフィールドが重複するなら、extends を使う。同じ型レベルの演算を繰り返すなら、ジェネリック型を使って型をマッピングする(項目50)。
型をマッピングする TypeScript の機能
keyof は型のキーのユニオンを返す(type OptionKeys = keyof Options)。typeof は値から型を作る(type Options = typeof INIT_OPTIONS)。ただし typeof を使うのは、Single Source of Truth として使いたい値があるときだけにする。通常は先に型を定義し、値がその型に代入できることを宣言したほうがよい。型が明示的になり、予期しない型の拡大(項目20)も起きにくくなる。
インデックスアクセス型を使うと、型の一部を取り出せる(type ActionType = Action['type'])。マップ型を使うと、既存の型のキーから新しい型を作れる。
type OptionsUpdate = { [k in keyof Options]?: Options[k] }マップ型で as を使うと、キーの名前を変えられる。これはテンプレートリテラル型と組み合わせると便利である(項目54)。
type LongToShort = { [k in keyof ShortToLong as ShortToLong[k]]: k }準同型なマップ型
インデックスの部分で K in keyof T を使うマップ型を、準同型(homomorphic)なマップ型と呼ぶ。準同型なマップ型では、readonly や ? などの修飾子や TSDoc のコメントが新しい型に引き継がれる。
interface Customer {
title?: string
readonly name: string
}
type PickName = Pick<Customer, "name"> // { readonly name: string; }
type ManualName = { [K in "name"]: Customer[K] } // { name: string; }型の再利用に役立つユーティリティ型
Pick<T, Keys>: 型Tから、Keysで指定したキーだけを持つ型を返すPartial<T>: 型Tのすべてのプロパティをオプションにした型を返すReturnType<T>: 関数型Tの戻り値の型を返す