교집합이 없는 타입들로만 만든 유니온 타입을 말한다.
태그를 붙혀서 객체를 구별할 수 있는 기능이기 때문에 태그드유니온타입 이라고도 한다.
string | number 와 같이 공통원소가 하나도 없는 관계를 수학에서는 서로소 관계에 있다고 한다.
이런 두개의 집합을 서로소 집합이라 한다.
아래 코드는 주석이 없을 경우 login 함수쪽만 보고서는 어떤 조건인지 직관적으로 알기가 어렵다.
type Admin = {
name: string;
kickCount: number;
};
type Member = {
name: string;
point: number;
};
type Guest = {
name: string;
visitCount: number;
};
type User = Admin | Member | Guest;
function login(user:User) {
if('kickCount' in user) { // 타입좁히기
// kickCount in user라는 조건만 보고 이 부분이 admin타입으로 좁혀졌다고 알기가 쉽지않다.
// 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}번 방문했습니다.`)
}
}
각각의 타입에 태그라는 프로퍼티를 리터럴 타입으로 추가한 후, 서로소관계에 있는 타입들을 유니온 타입으로 묶어 User2타입을 만들어서 사용하였다.
type Admin2 = {
tag:'ADMIN';
name: string;
kickCount: number;
};
type Member2 = {
tag:'MEMBER';
name: string;
point: number;
};
type Guest2 = {
tag:'GUEST';
name: string;
visitCount: number;
};
type User2 = Admin2 | Member2 | Guest2;
function login2(user:User2) {
if(user.tag ==='ADMIN') { // 타입별 tag 프로퍼티로 조건을 기술하니 더욱 직관적게 되었다.
// admin2 타입
console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`)
} else if (user.tag ==='MEMBER') {
// Member2 타입
console.log(`${user.name}님 현재까지 ${user.point}모았습니다.`)
} else {
// Guest2 타입
console.log(`${user.name}님 현재까지 ${user.visitCount}번 방문했습니다.`)
}
// 혹은 더 직관적으로 switch문을 사용할 수도 있다.
switch(user.tag){
case "ADMIN" : {
// admin2 타입
console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`)
break;
}
case "MEMBER" : {
// Member2 타입
console.log(`${user.name}님 현재까지 ${user.point}모았습니다.`)
break;
}
case "GUEST" : {
// Guest2 타입
console.log(`${user.name}님 현재까지 ${user.visitCount}번 방문했습니다.`)
break;
}
}
}
아래 코드는 비동기 작업결과를 처리하기위해 만든 객체이다.
type AsyncTask = {
state: 'LOADING' | "FAILED" | "SUCCESS";
error?: {
message: string;
};
response?: {
data: string;
}
}
const loading: AsyncTask = {
state: "LOADING"
};
const failed: AsyncTask = {
state: "FAILED",
error: {
message: '오류발생 원인은!! '
}
};
const success: AsyncTask = {
state: 'SUCCESS',
response: {
data: '데이터~'
}
}
문제는 아래 코드에 task.error.message처럼 접근했을때 task가 정상적으로 좁혀지지 않아서 에러가 난다. task.error의 프로퍼티가 선택적 프로퍼티로 되어있기 때문에 task에 에러가 있는지, 없는지 확실하게 알 수 없는 상황이라 그렇다.
function processResult(task: AsyncTask) {
switch(task.state) {
case "LOADING" : {
console.log('로딩중');
break
}
case "FAILED" : {
// 이때 task의 타입은 AsyncTask이다. failed타입으로 좁혀지지 않았다.
**console.log(`에러발생 : ${task.error.message}`);**
break;
}
case "SUCCESS" : {
console.log(`성공 : ${task.response?.data}`);
break;
}
}
}
그래서 부득이하게 옵셔널체이닝이나 non null단언을 사용해줘야하는데 이는 타입스크립트를 사용할때 그다지 안전한 코드가 아니라고 할 수 있다.
// 옵셔널 체이닝을 사용하거나
console.log(`에러발생 : ${task.error?.message}`);
// non null 단언을 사용해야하는 상황이다.
console.log(`에러발생 : ${task.error!.message}`);
break;