Skip to content

Latest commit

 

History

History
188 lines (151 loc) · 7.08 KB

union-of-interfaces.md

File metadata and controls

188 lines (151 loc) · 7.08 KB

Item 34: Prefer Unions of Interfaces to Interfaces with Unions

Things to Remember

  • 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.

Code Samples

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