[TypeScript] 서로소 유니온 타입
🍠 서로소 유니온 타입
교집합이 없는 타입들로만 만든 유니온 타입
type Admin = {
name: string;
kickCount: number;
};
type Member = {
name: string;
point: number;
};
type Guest = {
name: string;
visitCount: number;
};
type User = Admin | Member | Guest;
회원의 역할 분류에 따라 타입을 각각 분류한 후 3 타입의 합집합 타입인 User 타입을 만들었다.
회원의 역할에 따라 각각 다른 기능을 수행하도록 하는 함수 login을 만들어본다면
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}번 오셨습니다`);
}
}
조건문에 따라 회원의 역할을 구분해준다.
- user에 kickCount 프로퍼티가 있으므로 Admin 타입
- user에 point 프로퍼티가 있으므로 Member 타입
- Admin 타입과 Member 타입을 제외한 Guest 타입
하지만 이렇게 작성된 코드는 다른 사람이 보기에는 직관적이지 않아 바로 파악하기가 힘들다.
이러한 경우 각 타입에 tag(태그) 프로퍼티를 추가로 정의하여 각 타입을 서로소 집합으로 만들어준다.
type Admin = {
// 직관적으로 알아보기 위해 tag 추가
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;
function login(user:User){
// user.tag로 구분하기
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;
}
}
}
비동기 작업의 결과를 처리하는 객체를 만들어보자
const loading:AsyncTask = {
state: "LOADING",
};
const failed:AsyncTask = {
state: "FAILED",
error: {
message: "오류 발생",
}
};
const success:AsyncTask = {
state: "SUCCESS",
response: {
data: "데이터",
}
};
type AsyncTask = {
state: "LOADING" | "FAILED" | "SUCCESS" ; // string으로 정의하는 것보다 더 직관적
error?: {
message : string;
};
response?: {
data: string;
};
};
비동기 작업의 결과에 따라 각 다른 동작을 하는 함수를 만들어보자.
function processResult (task: AsyncTask){
switch(task.state){
case "LOADING":{
console.log("로딩 중");
break;
};
case "FAILED":{
console.log(`에러 발생 : ${task.error?.message}`);
break;
}
case "SUCCESS":{
console.log(`성공 : ${task.response?.data}`);
break;
}
}
}
여기에서 error와 response의 옵셔널 체이닝을 지우면 에러가 발생한다. "FAILED"와 "SUCCESS"로 타입 구분을 했는데 왜???
task의 타입은 좁혀질 타입이 없기 때문이다.
커서를 대보면 여전히 task의 타입은 AsyncTask 타입으로 정의되어있다. task의 state로 구분했는데 왜???
type AsyncTask = {
state: "LOADING" | "FAILED" | "SUCCESS" ;
error?: {
message : string;
};
response?: {
data: string;
};
};
AsyncTask에서 state로 구분을 해도 error와 response는 선택적 프로퍼티이기 때문에 값이 존재하는지 확실하지 않다. 따라서 좁혀질 타입이 존재하지 않는다.
그렇기 때문에 옵셔널 체이닝 또는 Non Null 단언을 해주어야 한다.
하지만 이는 안전한 코드가 아니다.
이를 해결하기 위해서는 AsyncTask를 서로소 유니온 타입으로 만들어준다.
// 서로소 유니온 타입으로 만들기
type LoadingTask = {
state: "LOADING";
};
type FailedTask = {
state: "FAILED";
error: {
message: string;
};
}
type SuccessTask = {
state: "SUCCESS";
response: {
data: string;
}
}
type AsyncTask = LoadingTask | FailedTask | SuccessTask;
서로소 유니온 타입으로 정의를 하면 상태에 따라 좁혀질 타입이 존재하므로 오류가 발생하지 않는다.
한 입 크기로 잘라먹는 타입스크립트 - 인프런 | 강의
문법을 넘어 동작 원리와 개념 이해까지 배워도 배워도 헷갈리는 타입스크립트 이제 제대로 배워보세요! 여러분을 타입스크립트 마법사🧙🏻♀️로 만들어드립니다., 프론트엔드의 피할 수
www.inflearn.com