[TYPESCRIPT] Discriminated Union 패턴으로 안전한 분기 처리 - 타입으로 보장하는 조건 분기

 

실무에서 상태값이나 응답 타입, 이벤트 타입을 처리하다 보면 “이 값이 어떤 형태인지”에 따라 분기해야 하는 경우가 매우 많습니다. 이때 단순한 if / else 분기만으로는 타입 안정성을 완전히 보장하기 어렵습니다.

TypeScript에서는 이러한 문제를 해결하기 위해 Discriminated Union(식별 가능한 유니온) 패턴을 제공합니다. 이 패턴을 사용하면 컴파일 단계에서 분기 누락과 잘못된 접근을 모두 차단할 수 있습니다.

 

Discriminated Union이란?

Discriminated Union은 다음 두 가지 조건을 만족하는 유니온 타입입니다.

  • 모든 타입이 공통된 식별자 필드를 가진다
  • 그 필드는 리터럴 타입이다
type Loading = {
  status: "loading";
};

type Success = {
  status: "success";
  data: string;
};

type Failure = {
  status: "error";
  message: string;
};

type Result = Loading | Success | Failure;

여기서 status가 바로 discriminant(식별자) 역할을 합니다.

 

안전한 분기 처리의 핵심

Discriminated Union의 가장 큰 장점은 조건 분기 안에서 타입이 자동으로 좁혀진다는 점입니다.

function handleResult(result: Result) {
  if (result.status === "success") {
    return result.data;      // Success 타입으로 자동 추론
  }

  if (result.status === "error") {
    return result.message;   // Failure 타입
  }

  return "로딩 중...";
}

별도의 타입 단언(as) 없이도 안전하게 필드 접근이 가능합니다.

 

switch + never 패턴 (실무 필수)

실무에서는 switch 문과 never를 함께 사용하는 패턴이 가장 권장됩니다.

function handle(result: Result): string {
  switch (result.status) {
    case "loading":
      return "로딩 중...";
    case "success":
      return result.data;
    case "error":
      return result.message;
    default:
      const _exhaustive: never = result;
      return _exhaustive;
  }
}

이 방식의 핵심 장점은 다음과 같습니다.

  • 새로운 상태가 추가되면 컴파일 에러 발생
  • 분기 누락을 코드 리뷰 이전에 차단
  • 타입 안정성과 유지보수성 모두 확보

대규모 코드베이스에서는 이 패턴이 사실상 표준입니다.

 

실전 예제 — 도형 계산

Discriminated Union은 수학적 모델링, 도메인 모델링에서도 자주 활용됩니다.

type Circle = {
  kind: "circle";
  radius: number;
};

type Square = {
  kind: "square";
  size: number;
};

type Rectangle = {
  kind: "rectangle";
  width: number;
  height: number;
};

type Shape = Circle | Square | Rectangle;

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.size ** 2;
    case "rectangle":
      return shape.width * shape.height;
    default:
      const _never: never = shape;
      return _never;
  }
}

각 분기에서 정확한 타입만 접근 가능하므로 계산 로직이 매우 안전해집니다.

 

API 응답 모델링에서의 활용

백엔드 API 응답을 모델링할 때 Discriminated Union은 특히 강력합니다.

type ApiResponse =
  | { ok: true; data: { id: number; name: string } }
  | { ok: false; errorCode: string };

function handleApi(res: ApiResponse) {
  if (res.ok) {
    return res.data.name;
  }
  return `에러 코드: ${res.errorCode}`;
}

ok 값 하나로 성공/실패 분기가 완전히 보장됩니다.

 

Discriminated Union을 써야 하는 이유

  • 타입 단언(as) 제거
  • null / undefined 체크 감소
  • 상태 추가 시 컴파일 에러로 즉시 감지
  • 도메인 모델이 명확해짐
  • 리팩토링 안정성 극대화

특히 상태가 늘어날 가능성이 있는 로직에서는 Discriminated Union이 사실상 유일한 정답에 가깝습니다.

 

실무 적용 시 주의사항

  • 식별자 필드는 반드시 리터럴 타입으로 선언
  • optional 식별자는 피할 것
  • 가능하면 switch + never 패턴 사용
  • if/else 중첩보다 가독성 높은 구조 유지

이 원칙만 지켜도 분기 관련 버그는 눈에 띄게 줄어듭니다.

 


 

 

Discriminated Union 패턴은 “타입으로 분기를 강제하는 설계 기법”입니다. 런타임에 발생할 수 있는 분기 오류를 컴파일 단계에서 차단함으로써 안정적이고 확장 가능한 코드를 만들 수 있습니다.

  • 공통 식별자 + 리터럴 타입
  • switch + never로 완전성 보장
  • 상태/이벤트/API 모델링에 최적