1. 조건적인 타입 선언을 위한 문법들
extends
제약 조건으로서 사용되는 extends
타입스크립트에서 제공하는 내장 제네릭 Pick
은 다음과 같이 구현할 수 있고, 사용된다.
type MyPick<T, U extends keyof T> = {
[key in U]: any;
}
type Person = {
name: string;
age: number;
address: string;
}
type PrivatePerson = MyPick<Person, 'name'>;
여기서, extends
가 사용된 타입 정의는 어떻게 해석할 수 있을까? 종종 머리가 복잡해지는 구문이다.
T extends U
타입 시스템을 집합으로 표현했을때,
'T는 U보다 작다.' 혹은 'T는 U의 부분집합이다.' 로 해석할 수 있다. 즉, T
의 범위를 U
로 제한하는 제약 조건을 둔 셈이다.
U extends keyof T
다시 Pick
에서 사용된 extends
의 구문을 보면, 'U는 T 객체의 키 집합으로 제한한다.' 혹은 'U는 T객체의 키 값의 부분 집합이다.'로 해석할 수 있다.
조건부로 사용되는 extends
타입 시스템에서 입력의 타입을 기반으로 출력의 타입을 결정할 수 있도록 타입 간의 관계를 열어두었다. 바로 extends
를 조건부로 사용할 수 있는데, 해석은 제약 조건과 같지만, 마치 'T는 U의 부분집합인가?'를 묻는 의문문으로 사용된다.
false extends boolean
'false' extends boolean
false는 boolean의 부분 집합인가?, 'false'는 boolean의 부분 집합인가? 답은 각각 true
, false
를 얻어낼 수 있다. 조건에서 얻어진 참, 거짓의 결과를 삼항 연산자와 함께 조건부로서 출력 타입을 결정할 수 있다.
T
에서 U
에 할당할 수 있는 타입을 제외하는 내장 제네릭 Exclude<T, U>
를 예시로 들어보자. 유니온된 타입 중 하나인 'number는 number의 부분집합인가?' 에 참이기 때문에 never
을 반환한다.
type MyExclude<T, U> = T extends U ? never : T;
type StringOnly = MyExclude<number | string, number> // expected to be 'string'
Infer
infer
은 extends
의 조건식에서 사용되는 타입을 꺼내 담고, 참으로 평가될때 전개되는 구문에서 사용할 수 있다. 타입스크립트 엔진이 런타임 상황에서 타입을 추론할 수 있도록 열어두고, 조건이 참이면 infer U
구문의 U
타입을 할당, 거짓이면 never
를 할당하여 무시한다.
T
에 할당된 타입이 함수일때, 해당 함수의 파라미터를 튜플 타입으로 반환하는 내장 제네릭 Parameters<T>
를 예시로 들어보자. Parameters
의 T
로서 가능한 타입을 함수로 제한하고, 제한된 함수 타입에 부합하는지에 대한 참 조건에서 U
로 명명된 타입을 사용한다.
type MyParameters<T extends (...args: any[]) => any> = T extends (
...args: infer U
) => any
? [...U]
: any;
type T0 = MyParameters<() => string>; //type is []
type T1 = MyParameters<(s: string) => void>; //type is [s: string]
type T4 = MyParameters<any>; //type is any
이해를 돕기 위해 또 다른 예시를 살펴보자. 가령 Promise
와 같이 래핑하는 타입 내부의 타입을 구하고 싶다면 어떻게 할 수 있을까? 재귀적으로 Promise
타입을 벗겨내서 내부 타입을 추론할 수 있다.
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T;
type X = Promise<string>;
type Y = Promise<{ field: number }>;
type Z = Promise<Promise<string | number>>;
type AwaitedX = MyAwaited<X>; //expected to be 'string'
type AwaitedY = MyAwaited<Y>; //expected to be '{ field: number }'
type AwaitedZ = MyAwaited<Z>; //expected to be 'string | number'
Last updated