TypeScript

[TypeScript] 서로소 유니온 타입 (Discriminated Union)

2025년 11월 27일
1
TypeScriptStudyTypeGuardPatternMatching

📑 목차

이번에는 타입 좁히기를 훨씬 더 직관적이고 안전하게 할 수 있는 서로소 유니온 타입에 대해 알아본다. 다른 말로는 구별된 유니온(Discriminated Union) 또는 태그된 유니온(Tagged Union) 이라고도 부른다.

1. 서로소 유니온 타입이란?

서로소 유니온 타입은 교집합이 없는 타입들, 즉 서로소 관계에 있는 타입들을 모아 만든 유니온 타입을 말한다.

기본적으로 문자열이나 숫자 리터럴 타입을 사용하여 각 타입을 명확히 구분할 수 있는 '태그(Tag)' 를 붙여서 만든다.

위 그림처럼 각 타입(Admin, Member, Guest)이 서로 겹치는 부분이 전혀 없게 만드는 것이 핵심이다.


2. 왜 필요한가? (문제 상황)

회원 관리 프로그램에서 Admin, Member, Guest 세 가지 역할을 구분해야 한다고 가정해 보자.

type Admin = {
  name: string;
  kickCount: number; // 강퇴 횟수
};
type Member = {
  name: string;
  point: number;     // 포인트
};
type Guest = {
  name: string;
  visitCount: number; // 방문 횟수
};

type User = Admin | Member | Guest;

이 상태에서 login 함수를 만들어 각 역할에 맞는 로그를 출력하려면 어떻게 해야 할까? 일반적인 방법으로는 in 연산자를 사용해 특정 프로퍼티가 있는지 확인해야 한다 .

function login(user: User) {
  if ("kickCount" in user) {
    // Admin
    console.log(`${user.name}님 현재까지 ${user.kickCount}명 추방했습니다`);
  } else if ("point" in user) {
    // Member
    console.log(`${user.name}님 현재까지 ${user.point} 모았습니다`);
  } else {
    // Guest
    console.log(`${user.name}님 현재까지 ${user.visitCount}번 오셨습니다`);
  }
}

문제점

  • 직관적이지 않음: 코드를 읽을 때 "kickCount"가 있으면 Admin이구나라고 한 번 더 생각해야 한다.
  • 복잡함: 타입이 늘어날수록 조건식이 복잡해지고 파악하기 어렵다 .

3. 해결책: 태그(Tag) 달기

이 문제를 해결하기 위해 각 타입에 고유한 리터럴 타입의 프로퍼티(tag) 를 추가한다. 이것이 서로소 유니온 타입의 핵심이다.

type Admin = {
  tag: "ADMIN"; // 태그 추가
  name: string;
  kickCount: number;
};

type Member = {
  tag: "MEMBER"; // 태그 추가
  name: string;
  point: number;
};

type Guest = {
  tag: "GUEST"; // 태그 추가
  name: string;
  visitCount: number;
};

type User = Admin | Member | Guest;

이제 각 타입은 tag라는 공통 프로퍼티를 가지지만, 그 값은 "ADMIN", "MEMBER", "GUEST"로 모두 다르다 . 즉, 서로 교집합이 없는 완벽한 서로소 관계가 되었다.


4. 더 직관적인 타입 좁히기

이제 in 연산자 대신 tag를 확인하면 된다. 코드가 훨씬 직관적으로 변한다 .

4-1. if문 사용

function login(user: User) {
  if (user.tag === "ADMIN") {
    // 타입스크립트는 이제 user가 확실히 Admin임을 안다
    console.log(`${user.name}님 현재까지 ${user.kickCount}명 추방했습니다`);
  } else if (user.tag === "MEMBER") {
    console.log(`${user.name}님 현재까지 ${user.point} 모았습니다`);
  } else {
    console.log(`${user.name}님 현재까지 ${user.visitCount}번 오셨습니다`);
  }
}

4-2. switch문 사용 (추천)

서로소 유니온 타입은 switch 문과 함께 사용할 때 가장 빛을 발한다. 가독성이 매우 좋아진다 .

function login(user: User) {
  switch (user.tag) {
    case "ADMIN": {
      console.log(`${user.name}님 현재까지 ${user.kickCount}명 추방했습니다`);
      break;
    }
    case "MEMBER": {
      console.log(`${user.name}님 현재까지 ${user.point} 모았습니다`);
      break;
    }
    case "GUEST": {
      console.log(`${user.name}님 현재까지 ${user.visitCount}번 오셨습니다`);
      break;
    }
  }
}

요약

  1. 서로소 유니온 타입: tag 같은 식별 가능한 프로퍼티를 두어 타입 간의 교집합을 없앤 유니온 타입이다.
  2. 장점:
    • 코드가 훨씬 직관적이다 (kickCount가 있는지 확인하는 것보다 tagADMIN인지 확인하는 것이 명확함).
    • 타입 좁히기가 안전하고 정확하게 동작한다.
    • switch 문을 이용해 깔끔한 분기 처리가 가능하다.
  3. 활용: 리액트의 reducer 액션 객체나 서버 응답 상태 처리 등 다양한 곳에서 필수적으로 사용되는 패턴이다.
@taemni

@taemni

안녕하세요, 차근차근 성장 중인 조태민입니다.

instagram

댓글