Item 32 유니온의 인터페이스보다는 인터페이스의 유니온을 사용하기

유니온 타입의 속성이 아니라 인터페이스 자체를 유니온으로 사용하는 걸 고려하자

유니온 타입의 속성(e.g., A | B)을 갖는 인터페이스를 작성 중이라면, 혹시 인터페이스의 유니온 타입을 사용하는 게 더 알맞지 않은지 검토해보자. 다음은 태그된 유니온의 예시이다.

interface Layer {
  type: 'fill' | 'line' | 'point'
  layout: FillLayout | LineLayout | PointLayout
  paint: FillPaint | LinePaint | PointPaint
}

layoutLineLayout이면서 paint 속성이 FillPaint일 순 없다. 이보단 각 타입의 계층을 분리하는 게 나을 것이다.

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 | PointLayer

이런 식으로 작성하면 layoutpaint 속성이 잘못된 조합으로 섞이는 걸 막을 수 있다.

Item 28 유효한 상태만 표현하는 타입을 지향하기의 조언에 따라 유효한 상태만을 표현하도록 타입을 정의했다.

이런 식으로 타입의 범위를 좁힐 수 있을 것이다.

function drawLayer(layer: Layer) {
  if (layer.type === 'fill') {
    const { paint } = layer  // 타입이 FillPaint
    const { layout } = layer // 타입이 FillLayout
  } else if (layer.type === 'line') {
    const { paint } = layer  // 타입이 LinePaint
    const { layout } = layer // 타입이 LineLayout
  } else {
    const { paint } = layer  // 타입이 PointPaint
    const { layout } = layer // 타입이 PointLayout
  }
}

태그된 유니온

어떤 데이터 타입을 태그된 유니온으로 표현할 수 있다면, 보통은 그렇게 하는 것이 좋다.

또는 여러 개의 선택적 필드가 동시에 값이 있거나 동시에 undefined인 경우도 태그된 유니온 패턴이 잘 맞는다.

interface Person {
  name: string
  // 둘 다 있거나 동시에 없다
  placeOfBirth?: string
  dateOfBirth?: Date
}

타입 정보를 담고 있는 주석은 문제가 될 소지가 매우 높다. (Item 30 문서에 타입 정보를 쓰지 않기)

두 개의 속성을 하나의 객체로 모으자. 이 방법은 null 값을 경계로 두는 방법과 비슷하다. (Item 31 타입 주변에 null 값 배치하기)

interface Person {
  name: string
  birth?: {
    place: string
    date: Date
  }
}

이렇게 하면 둘 중 하나라도 없으면 오류가 날 것이다.

타입의 구조를 손댈 수 없다면

타입의 구조를 손댈 수 없는 상황(e.g., API의 결과)이면, 앞서 다룬 인터페이스의 유니온을 사용해 속성 사이의 관계를 모델링할 수 있다.

interface Name {
  name: string
}

interface PersonWithBirth extends Name {
  placeOfBirth: string
  dateOfBirth: Date
}

type Person = Name | PersonWithBirth

이제 중첩된 객체에서도 동일한 효과를 볼 수 있다.

function eulogize(p: Person) {
  if ('placeOfBirth' in p) {
    p                          // 타입이 PersonWithBirth로 추론됨
    const { dateOfBirth } = p  // OK, 타입이 Date
  }
}

Summary

  • 유니온 타입의 속성을 여러 개 갖는 인터페이스에서는 속성 간의 관계가 분명하지 않으므로 실수가 자주 발생할 수 있으니 주의하자.

  • 유니온의 인터페이스보다 인터페이스의 유니온이 더 정확하고 타입 스크립트가 이해하기 좋다.

  • 타입스크립트가 제어 흐름을 분석할 수 있도록 타입에 태그를 넣는 것(태그된 유니온)을 고려하자.

Last updated