TL;DR
- ユニオン型のプロパティを複数持つインタフェースは、プロパティ間の関係を表せていないことがある。その場合はインタフェースのユニオンにする。
- 制御フロー解析で絞り込みやすくするため、タグ付きユニオンを使うとよい。
- 一緒に存在する複数のオプションプロパティは、1 つのオプションプロパティにまとめる。
内容
インタフェースのユニオンにする
ユニオン型のプロパティを複数持つインタフェースは、プロパティ間の関係を明確に表せていないことがある(項目29)。次の Layer では、layout が FillLayout なのに paint が LinePaint という組み合わせも許してしまう。
// NG
interface Layer {
layout: FillLayout | LineLayout | PointLayout
paint: FillPaint | LinePaint | PointPaint
}この場合は、代わりにインタフェースのユニオンを検討する。
// OK
interface FillLayer {
layout: FillLayout
paint: FillPaint
}
interface LineLayer {
layout: LineLayout
paint: LinePaint
}
interface PointLayer {
layout: PointLayout
paint: PointPaint
}
type Layer = FillLayer | LineLayer | PointLayerタグ付きユニオンを使う
制御フロー解析で型を絞り込みやすくするため、タグ付きユニオンを使うとよい。このパターンは TypeScript で頻繁に使われる。
interface FillLayer {
type: "fill"
layout: FillLayout
paint: FillPaint
}
interface LineLayer {
type: "line"
layout: LineLayout
paint: LinePaint
}
interface PointLayer {
type: "point"
layout: PointLayout
paint: PointPaint
}
type Layer = FillLayer | LineLayer | PointLayerconst drawLayer = (layer: Layer) => {
if (layer.type === "fill") {
const { paint } = layer
// ^? const paint: FillPaint
const { layout } = layer
// ^? const layout: FillLayout
}
// ...
}オプションプロパティをまとめる
データを正確にモデリングするため、複数のオプションプロパティを 1 つにまとめられないか検討する。次の Person では、placeOfBirth と dateOfBirth は両方存在するか、両方存在しないかのどちらかだが、型からはそれが分からない。
// NG
interface Person {
name: string
placeOfBirth?: string
dateOfBirth?: Date
}// OK
interface Person {
name: string
birth?: {
placeOfBirth: string
dateOfBirth: Date
}
}データ構造を変えられない場合は、インタフェースのユニオンを使えばよい。
interface Name {
name: string
}
interface PersonWithBirth extends Name {
placeOfBirth: string
dateOfBirth: Date
}
type Person = Name | PersonWithBirth