📑 목차
이번에는 타입 좁히기를 훨씬 더 직관적이고 안전하게 할 수 있는 서로소 유니온 타입에 대해 알아본다. 다른 말로는 구별된 유니온(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;
}
}
}
요약
- 서로소 유니온 타입:
tag같은 식별 가능한 프로퍼티를 두어 타입 간의 교집합을 없앤 유니온 타입이다. - 장점:
- 코드가 훨씬 직관적이다 (
kickCount가 있는지 확인하는 것보다tag가ADMIN인지 확인하는 것이 명확함). - 타입 좁히기가 안전하고 정확하게 동작한다.
switch문을 이용해 깔끔한 분기 처리가 가능하다.
- 코드가 훨씬 직관적이다 (
- 활용: 리액트의
reducer액션 객체나 서버 응답 상태 처리 등 다양한 곳에서 필수적으로 사용되는 패턴이다.
