TypeScript 식별 유니언 패턴(Discriminated Union)에 대해서

OH-HAIO·2025년 4월 12일
0

typescript

목록 보기
4/5

TypeScript Discriminated Union(식별 유니언) 패턴

1. Discriminated Union(식별 유니언)이란?

Discriminated Union(식별 유니언)은서로 다른 타입들을 하나의 유니언 타입으로 지정하고, 각 타입을 구분할 수 있는 "공통 식별자" 필드를 두는 방식입니다.

1.1. 기본 예시

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; size: number }
  | { kind: "rectangle"; width: number; height: number };
  • 모든 타입에 kind 라는 고정된 literal 값을 가진 필드를 지정합니다.
  • kind를 기준으로 타입을 정확하게 판별 하는것이 가능합니다.

1.2. Discriminated Union을 왜 쓰는가?

일반 유니언 사용시 문제점 Discriminated Union 사용시 이점
각 타입 판별을 수동으로 해야함kind필드로 타입 좁히기 가능
잘못된 속성 접근 위험 존재타입 스크립트가 자동 감지 및 경고
조건 분기 시 가독성이 낮음switch-case로 명확하게 분리
새로운 타입 추가 시 누락 방지 어려움nerver체크로 누락 감지 가능

2. 실전 예제

2.1. UI 상태 처리

Before : 조건문 + any 타입 처리

function renderStatus(status: any) {
  if (status === "loading") return "로딩 중...";
  if (status === "error") return "오류 발생";
  if (status === "success") return "성공!";
  return "알 수 없음";
}
  • 타입 안정성이 떨어 집니다.
  • 오타나 누락이 있어도 컴파일러가 감지를 할 수 없습니다.

After : Discriminated Union 적용

type Status =
  | { type: "loading" }
  | { type: "error"; message: string }
  | { type: "success"; data: string };

function renderStatus(status: Status): string {
  switch (status.type) {
    case "loading":
      return "로딩 중...";
    case "error":
      return `${status.message}`;
    case "success":
      return `${status.data}`;
    default:
      const _exhaustive: never = status;
      return _exhaustive;
  }
}
  • status.type을 기준으로 타입이 정확히 좁힐수 있습니다.
  • status.message, status.data 접근 시 오류가 발생하지 않습니다.
  • default에서 never 타입 체크로 새로운 case 누락 방지 가능합니다.

2.2. 다양한 알림(Notification) 유형 처리

Before : 단순 조건문, 타입 보장 없음

function handleNotification(notification: any) {
  if (notification.type === "email") {
    sendEmail(notification.address);
  } else if (notification.type === "sms") {
    sendSMS(notification.phoneNumber);
  } else if (notification.type === "push") {
    sendPush(notification.deviceId);
  }
}
  • notification.address, notification.deviceId 등 타입 보장이 없습니다.
  • 실수로 notification.phone이라고 썼어도 에러를 잡을 수 없습니다.

After : Discriminated Union 적용

type Notification =
  | { type: "email"; address: string }
  | { type: "sms"; phoneNumber: string }
  | { type: "push"; deviceId: string };

function handleNotification(notification: Notification) {
  switch (notification.type) {
    case "email":
      sendEmail(notification.address);
      break;
    case "sms":
      sendSMS(notification.phoneNumber);
      break;
    case "push":
      sendPush(notification.deviceId);
      break;
    default:
      const _exhaustive: never = notification;
      return _exhaustive;
  }
}
  • 각 케이스마다 정확한 속성만 접근 가능 합니다.
  • notification.phone 같은 오타는 즉시 컴파일 에러가 발생합니다.
  • 새로운 타입 추가 시 default에서 감지가 가능합니다.

3. 언제 사용하면 좋은가

  • 상태(state)가 여러 가지 형태로 분기될 때
  • API 응답이 다양한 형태일 때
  • 이벤트나 액션의 타입이 분리되어야 할 때
  • 조건 분기를 안전하고 예측 가능하게 하고 싶을 때

0개의 댓글