본문 바로가기

main/JS | TS

타입 확장하기 (Union/Intersection Type), 좁히기 (Type Guard)

타입 확장하기

타입스크립트에서는 중복되는 타입 선언을 줄이기 위해 "타입 확장"을 한다.

interface A {
  name: string;
  url: string;
}

interface B extends A {
  time: number;
}

type C = {
  name: string;
  url: string;
};

type D = {
  time: number;
} & C;

 

extends 외에도 |, & 기호를 이용해서 유니온 타입, 교차 타입을 사용할 수 있다.

 

유니온 타입 (Union Type)

 

유니온 타입은 유니온 타입에 포함된 모든 타입이 공통으로 갖고 있는 속성에만 접근할 수 있다.

두 타입의 공통 속성은 orderId뿐이다.

step.distance를 찾게 되면 에러가 발생한다.

 

하지만 책에서는 이 개념을 합집합이라고 설명하는데 ..

(p.123) 타입스크립트의 타입을 속성의 집합이 아니라 값의 집합이라고 생각해야 유니온 타입이 합집합이라는 개념을 이해할 수 있다.

type MyUnion = string | number;

위와 같은 타입이라면 MyUnion 타입을 가지는 변수는 string 이거나 number 둘 중 어떤 것도 될 수 있다. (합집합)

하지만 객체의 경우는 아니다. 두 객체의 공통 속성 (따지자면 속성의 교집합이 아닌가?)에 해당하는 값에만 접근할 수 있다.

값의 합집합이라고 하면, 서로 다른 타입에서 동일하게 소유하고 있지 않는 속성을 설명하기가 애매해진다.

 

나는 이 유니온/교차 타입의 개념을 이해하는 것보다 이 책의 설명이 너무 어려웠다. 이해하기 위해 이부분 세 번을 읽었음..

그리고 내린 결론, 유니온/교차 타입을 집합에 비유할 필요가 없다.

집합과는 같은 개념이 아닌데 오히려 집합으로 이해하려고 하니 어려워지기만 하는 것 같다.

 

단순히 and, or로 보면 된다. | 의 경우 흔히 알듯이 or 이다.

MyUnion의 경우 string 이거나, number이다. 어떤 것도 될 수 있지만 둘 중 어떤 것이 될 지 모른다.

CookingStep 이거나, DeliveryStep이다. 하지만 이 둘 중 어떤 것이 될 지 모른다. 근데 둘 중 하나만 될 수 있으니까 안전하게 둘 다 가지고 있는 속성만 허용하자!

라는 느낌 아닐까? (뇌피셜임)

 

교차 타입 (Intersection Type)

BaedalProgress는 CookingStep과 DeliveryStep 타입을 합쳐 모든 속성을 가진 단일 타입이 된다.

객체에서 볼 때... 이게 어떻게 교집합이란 말인가 ............? (p.124, 교차 타입은 교집합의 개념과 비슷하다, 물론 속성 말고 값을 보라고는 했으나 그럼 예시는 이게 아니라 다른 걸 들었어야 될 것 같다.)

 

객체가 아닌 아래 예시의 경우 유니온 타입과 같이 집합에 비유하면 교집합과 비슷한 성격이 보인다.

IdType과 Numeric의 공통적인(교집합) number가 Universal의 타입이 된다.

여기서 만약 IdType의 number를 지워버린다면 Universal은 never 타입이 된다.

 

유니온 타입과 마찬가지로 단순히 and로 생각해보자.

IdType이면서 Numeric 이어야한다. number가 될 수 있다.

CookingStep이면서 DeliveryStep이어야 한다. 모든 속성을 포함한다.

훨씬 간단.. 하지 않나?

 

두 객체에서의 속성이 같은데 값의 타입이 다른 경우를 보자.

각 타입의 orderId는 string, number로 다르다. 이를 &를 통해 교차 타입을 만들어 버리면,

string이면서 number가 될 수 없기 때문에 orderId는 never가 된다.

반대로 |를 통해 유니온 타입을 만들어 버리면,

string이거나 number가 들어올 수 있게 된다.

 

extends와 교차 타입

유니온 타입과 교차 타입을 사용한 새로운 타입은 오직 type 키워드로만 선언할 수 있다.

그리고 이는 extends 키워드를 사용한 타입과 100% 상응하지 않는다.

interface로 extends 했을 경우 같은 tip 속성에 대하여 string이 number에 assign할 수 없다는 에러를 뱉고 있다.

교차 타입 & 를 사용했을 경우 string & number = never 이므로 tip이 never이 된다.

유니온 타입 | 를 사용했을 경우 string | number 타입이 된다.

 

 

타입 좁히기

특정 문맥 안에서 타입스크립트가 해당 변수를 타입 A로 추론하도록 유도하면서, 런타임에도 유효한 방법인 타입 가드를 설명한다.

크게 자바스크립트 연산자를 사용한 (typeof, instanceof, in) 방법과 사용자 정의 타입 가드가 있다.

 

is 연산자를 사용한 사용자 정의 타입 가드

타입 명제는 A is B 형식으로 작성한다. A는 매개변수 이름, B는 타입이다.

참/거짓의 진릿값을 반환하면서 반환 타입을 타입 명제로 지정하게 되면 반환 값이 참일 때 A 매개변수의 타입을 B 타입으로 취급한다.

isDestinationCode의 반환값은 boolean이다. 이 값이 참일 때, x는 DestinationCode 타입이다.

때문에 isDestinationCode(item)가 true일 때의 if문 내의 item은 타입이 DestinationCode로 추론된다.

else일 때 isDestinationCode(item)가 false이므로 item의 타입은 string으로 추론된다.

 

또 이 타입 가드와 NonNullable 타입을 이용해서 null, undefined를 검사해주는 함수를 만들어 쓸 수 있다.

checkNonNullable 함수에서 return되는 값이 true라면 (null도 undefined도 아니다), 해당 value 타입을 그대로 가진다.

하지만 return되는 값이 false라면 (null이거나 undefined이다), never 타입을 가진다.

 

Exhaustieness Checking

타입 가드를 사용해서 타입에 대한 분기 처리를 수행하면 필요하다고 생각되는 부분만 분기 처리를 하여 요구 사항에 맞는 코드를 작성할 수 있게 된다.

exhaustiveCheck() 함수는 매개변수를 never 타입으로 선언하고 있다. 즉, 매개변수로 어떤 값도 받을 수 없으며 값이 들어오면 에러를 뱉는다.

if 분기 처리로 모든 값을 검사하고 마지막 else에 exhaustiveCheck()를 사용해주어서 모든 타입에 대한 분기 처리를 강제할 수 있다.

 

 

출처:

책 [우아한 타입스크립트 with 리액트 - 우아한형제들 웹프론트개발그룹]