구별된 유니온

구별된 유니온(Discriminated Union)

리터럴 멤버 속성이 있는 클래스가 있다면 그 속성으로 유니온 구성원을 구별할 수 있습니다.

예제로 Square 타입과 Rectangle 타입의 유니온을 생각해봅시다. 여기에 kind라는 멤버 속성이 있는데, 이 속성은 모든 유니온 구성원에 존재하는 리터럴 타입입니다:

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
type Shape = Square | Rectangle;

이런 경우, 구별 속성(여기선 kind)에 대해 타입 가드 스타일의 검사 (==, ===, !=, !==) 또는 switch를 사용하면 TypeScript가 특정한 리터럴을 가진 객체를 대상으로 한다는 것을 알아채고 타입 좁히기를 실행해줍니다 :)

function area(s: Shape) {
    if (s.kind === "square") {
        // 이것으로 TypeScript가 `s`가 `Square`임을 알게 됨 ;)
        // 그러므로 `Square`의 멤버를 안전하게 사용할 수 있음 :)
        return s.size * s.size;
    }
    else {
        // `Square`가 아님? 그러면 TypeScript는
        // 이것이 `Rectangle`일 수 밖에 없음을 알게 됨 ;)
        // 그러므로 `Rectangle`의 멤버를 안전하게 사용할 수 있음 :)
        return s.width * s.height;
    }
}

빠짐없는 검사(Exhaustive Check)

매우 자주 유니온의 모든 구성원들이 어떤 코드(동작)를 가지고 있음을 보장하고 싶어집니다.

예를 들어 조치가 필요한 부분:

간단하게, 조건에 걸리지 않는 블럭을 하나 추가하고 그 블럭에서 추론된 타입이 never 타입과 호환되는 것으로 정의하면 됩니다. 예제처럼 완전(exhaustive) 검사를 추가하면 보기좋게 오류가 발생합니다:

그러면 새로 추가된 경우를 처리하며 됩니다:

Switch

팁: 당연히 switch 문에서 동일하게 할 수 있습니다:

strictNullChecks

만약 strictNullChecks를 사용하고 완전 검사를 사용한다면, TypeScript가 "not all code paths return a value"라는 오류를 발생시킬 수 있습니다. 이건 간단히 _exhaustiveCheck (never 타입) 변수를 반환하면 해결됩니다, 이렇게:

완전 검사에서 Throw

never 타입을 받는 함수 (따라서 변수의 타입이 never로 추론될 때만 호출되는 함수)를 만들고 함수 본문이 실행될 때 오류를 던질(throw) 수 있습니다:

면적 계산 함수에서 사용하는 예제:

회고적(Retrospective) 버전 관리

이런 형태의 자료 구조가 있다고 하고:

DTO를 잔뜩 만든 다음에 name이란 선택이 마음에 들지 않게 될 수 있습니다. 이런 경우 리터럴 숫자(원한다면 문자열도 가능)로 된 새로운 유니온을 만들어서 DTO의 버전을 식별할 수 있습니다. 버전 0을 undefined로 표기하고 strictNullChecks를 활성화시키면 그냥 됩니다:

이렇게 DTO를 사용하는 예제:

Redux

이 기능을 활용하는 유명 라이브러리가 redux 입니다.

이것은 리덕스의 주요 내용에 TypeScript 타입 어노테이션이 추가된 모습입니다:

TypeScript를 사용하면 오타로 인한 오류를 방지할 수 있고 리팩터링 편의성이 향상되며 코드가 좀더 자체 문서화되게 할 수 있습니다.

Last updated

Was this helpful?