Item 34: Prefer Unions of Interfaces to Interfaces with Unions
Interfaces with multiple properties that are union types are often a mistake because they obscure the relationships between these properties.
Unions of interfaces are more precise and can be understood by TypeScript.
Use tagged unions to facilitate control flow analysis. Because they are so well supported, this pattern is ubiquitous in TypeScript code.
Consider whether multiple optional properties could be grouped to more accurately model your data.
interface Layer {
layout : FillLayout | LineLayout | PointLayout ;
paint : FillPaint | LinePaint | PointPaint ;
}
💻 playground
interface FillLayer {
layout : FillLayout ;
paint : FillPaint ;
}
interface LineLayer {
layout : LineLayout ;
paint : LinePaint ;
}
interface PointLayer {
layout : PointLayout ;
paint : PointPaint ;
}
type Layer = FillLayer | LineLayer | PointLayer ;
💻 playground
interface Layer {
type : 'fill' | 'line' | 'point' ;
layout : FillLayout | LineLayout | PointLayout ;
paint : FillPaint | LinePaint | PointPaint ;
}
💻 playground
interface FillLayer {
type : 'fill' ;
layout : FillLayout ;
paint : FillPaint ;
}
interface LineLayer {
type : 'line' ;
layout : LineLayout ;
paint : LinePaint ;
}
interface PointLayer {
type : 'paint' ;
layout : PointLayout ;
paint : PointPaint ;
}
type Layer = FillLayer | LineLayer | PointLayer ;
💻 playground
function drawLayer ( layer : Layer ) {
if ( layer . type === 'fill' ) {
const { paint} = layer ;
// ^? const paint: FillPaint
const { layout} = layer ;
// ^? const layout: FillLayout
} else if ( layer . type === 'line' ) {
const { paint} = layer ;
// ^? const paint: LinePaint
const { layout} = layer ;
// ^? const layout: LineLayout
} else {
const { paint} = layer ;
// ^? const paint: PointPaint
const { layout} = layer ;
// ^? const layout: PointLayout
}
}
💻 playground
interface Person {
name : string ;
// These will either both be present or not be present
placeOfBirth ?: string ;
dateOfBirth ?: Date ;
}
💻 playground
interface Person {
name : string ;
birth ?: {
place : string ;
date : Date ;
}
}
💻 playground
const alanT : Person = {
name : 'Alan Turing' ,
birth : {
// ~~~~ Property 'date' is missing in type
// '{ place: string; }' but required in type
// '{ place: string; date: Date; }'
place : 'London'
}
}
💻 playground
function eulogize ( person : Person ) {
console . log ( person . name ) ;
const { birth} = person ;
if ( birth ) {
console . log ( `was born on ${ birth . date } in ${ birth . place } .` ) ;
}
}
💻 playground
interface Name {
name : string ;
}
interface PersonWithBirth extends Name {
placeOfBirth : string ;
dateOfBirth : Date ;
}
type Person = Name | PersonWithBirth ;
💻 playground
function eulogize ( person : Person ) {
if ( 'placeOfBirth' in person ) {
person
// ^? (parameter) person: PersonWithBirth
const { dateOfBirth} = person ; // OK
// ^? const dateOfBirth: Date
}
}
💻 playground