본문 바로가기
강의 실습/한 입 크기로 잘라먹는 타입스크립트(TypeScript)

서로소 유니온 타입

by Jint 2024. 10. 30.

한 입 크기로 잘라먹는 타입스크립트 - 서로소 유니온 타입

1. 서로소 유니온 타입
교집합이 없는 타입들로만 만든 유니온 타입.
ex) string | number

- src/chapter8.ts

/**
 * 서로소 유니온 타입
 */
type Admin = {
    name: string;
    kickCount: number;
};

type Member = {
    name: string;
    point: number;
};

type Guest = {
    name: string;
    visitCount: number;
};

// Union 타입
type User = Admin | Member | Guest;

// 로그인 함수
// Admin -> {name}님 현재까지 {kickCount}명 강퇴했습니다.
// Member -> {name}님 현재까지 {point} 모았습니다.
// Guest -> {name}님 현재까지 {visitCount}번 오셨습니다.
function login(user: User) {
    // in 연산자 활용한 타입 가드
    if ('kickCount' in user) {
        // user; // Admin 타입
        console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`);
    } else if ('point' in user) {
        // user; // Member 타입
        console.log(`${user.name}님 현재까지 ${user.point} 모았습니다.`);
    } else {
        // user; // Guest 타입
        console.log(`${user.name}님 현재까지 ${user.visitCount}번 오셨습니다.`);
    }
}


하지만 위의 내용은 직관적이지가 않아, 다른 사용자가 보면 혼동할 수가 있다.
이 때 서로소 유니온 타입을 활용한다.
string 리터럴 타입끼리는 교집합이 없는 공집합인 점을 활용한다.

- src/chapter8.ts

/**
 * 서로소 유니온 타입
 */
type Admin = {
    tag: 'ADMIN'; // string 리터럴 타입
    name: string;
    kickCount: number;
};

type Member = {
    tag: 'MEMBER'; // string 리터럴 타입
    name: string;
    point: number;
};

type Guest = {
    tag: 'GUEST'; // string 리터럴 타입
    name: string;
    visitCount: number;
};

// 서로소 Union 타입
type User = Admin | Member | Guest;

// 로그인 함수
// 서로소 유니온 타입 : 직관적
function login(user: User) {
    switch (user.tag) {
        case 'ADMIN': { // ADMIN 타입으로 좁혀짐
            console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`);
            break;
        }
        case 'MEMBER': { // MEMBER 타입으로 좁혀짐
            console.log(`${user.name}님 현재까지 ${user.point} 모았습니다.`);
            break;
        }
        case 'GUEST': { // GUEST 타입으로 좁혀짐
            console.log(`${user.name}님 현재까지 ${user.visitCount}번 오셨습니다.`);
            break;
        }
    }
    /*
    if (user.tag === '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}번 오셨습니다.`);
    }
    */
}


서로소 유니온 타입을 사용하면 좋은점을 복습겸 한가지 더 사례를 살펴본다.

- src/chapter8.ts

...
// 복습겸 한가지 더 사례
// 비동기 작업의 결과를 처리하는 객체
type AsyncTask = {
    state: 'LOADING' | 'FAILED' | 'SUCCESS';
    // state 가 'LOADING' 일 때를 대비해 선택적 프로퍼티로 만듦
    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;
        }
    }
}

const loading: AsyncTask = {
    state: 'LOADING'
};

const failed: AsyncTask = {
    state: 'FAILED'
  , error: {
        message: '오류 발생 원인은 ~~'
    }
};

const success: AsyncTask = {
    state: 'SUCCESS'
  , response: {
        data: '데이터 ~~'
    }
};


processResult() 함수 안에서 옵셔널 체이닝을 지우면 에러가 발생하는데, 선택적 프로퍼티이기 때문에 더 이상 좁혀질 타입이 없기 때문이다.
따라서 옵셔널 체이닝 또는 타입 non null 단언을 사용해야 하나, 이 방법은 안전하지 않다.
이 때 AsyncTask 를 3개의 타입으로 분리하여 서로소 유니온 타입으로 만든다.

- src/chapter8.ts

...
// 복습겸 한가지 더 사례
type LoadingTask = {
    state: 'LOADING'
};

type FailedTask = {
    state: 'FAILED';
    error: {
        message: string;
    }
};

type SuccessTask = {
    state: 'SUCCESS';
    response: {
        data: string;
    }
};

// 서로소 유니온 타입
type AsyncTask = LoadingTask | FailedTask | SuccessTask;

// 비동기 작업 처리 결과를 매개변수로 받아 상태에 따라 처리
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;
        }
    }
}

const loading: AsyncTask = {
    state: 'LOADING'
};

const failed: AsyncTask = {
    state: 'FAILED'
  , error: {
        message: '오류 발생 원인은 ~~'
    }
};

const success: AsyncTask = {
    state: 'SUCCESS'
  , response: {
        data: '데이터 ~~'
    }
};


동시에 여러가지 상태를 표현해야 하는 객체의 타입을 정의할 때, 선택적 프로퍼티를 사용하는 것보다 서로소 유니온 타입으로 만들어주는게 좋다.
그래야 switch case 문 같은 것을 이용했을 때 더 직관적으로 안전하게 타입을 좁혀서 코드를 만들 수 있기 때문이다.
또한 서로소 유니온 타입은 tag 를 붙여 객체를 완벽히 구별해줄 수 있는 기능이기 때문에 tagged 유니온 타입 이라고도 부른다.


참고링크 : https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8#

 

한 입 크기로 잘라먹는 타입스크립트(TypeScript) 강의 | 이정환 Winterlood - 인프런

이정환 Winterlood | 문법을 넘어 동작 원리와 개념 이해까지 배워도 배워도 헷갈리는 타입스크립트 이제 제대로 배워보세요! 여러분을 타입스크립트 마법사🧙🏻‍♀️로 만들어드립니다., 프론

www.inflearn.com

'강의 실습 > 한 입 크기로 잘라먹는 타입스크립트(TypeScript)' 카테고리의 다른 글

함수 타입 표현식과 호출 시그니쳐  (1) 2024.11.01
함수 타입  (3) 2024.10.31
타입 좁히기  (0) 2024.10.29
타입 단언  (0) 2024.10.28
타입 추론  (0) 2024.08.12

댓글