[TypeScript] 서로소 유니온 타입 (Discriminated Union)
이번에는 타입 좁히기를 훨씬 더 직관적이고 안전하게 할 수 있는 서로소 유니온 타입에 대해 알아본다. 다른 말로는 구별된 유니온(Discriminated Union) 또는 태그된 유니온(Tagged Union) 이라고도 부른다. 1. 서로소 유니온 타입이란? 서로소 유니온 타입은 교집합이 없는 타입들, 즉 서로소 관계에 있는 타입들을 모아 만든 유니온 타입을 말한다. 기본적으로 문자열이나 숫자 리터럴 타입을 사용하여 각 타입을 명확히 구분할 수 있는 '태그(Tag)' 를 붙여서 만든다. 위 그림처럼 각 타입(Admin, Member, Guest)이 서로 겹치는 부분이 전혀 없게 만드는 것이 핵심이다. --- 2. 왜 필요한가? (문제 상황) 회원 관리 프로그램에서 Admin, Member, Guest 세 가지 역할을 구분해야 한다고 가정해 보자. TypeScript 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 연산자를 사용해 특정 프로퍼티가 있는지 확인해야 한다 . TypeScript 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) 를 추가한다. 이것이 서로소 유니온 타입의 핵심이다. TypeScript 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문 사용 TypeScript 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 문과 함께 사용할 때 가장 빛을 발한다. 가독성이 매우 좋아진다 . TypeScript 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가 있는지 확인하는 것보다 tag가 ADMIN인지 확인하는 것이 명확함). - 타입 좁히기가 안전하고 정확하게 동작한다. - switch 문을 이용해 깔끔한 분기 처리가 가능하다. 3. 활용: 리액트의 reducer 액션 객체나 서버 응답 상태 처리 등 다양한 곳에서 필수적으로 사용되는 패턴이다.