배운 것을 바탕으로 타입을 만들어보자
이 포스트에서는 지금까지 배운 타입스크립트의 개념들을 바탕으로 실전에서 유용하게 사용할 수 있는 타입들을 직접 만들어보려 한다. 타입 간의 관계를 판단하거나, 집합 연산처럼 타입을 조작하는 기법들을 통해 타입스크릡트의 특징을 알아보자
타입을 판단하는 유틸리티 타입
타입스크립트에서는 어떤 타입이 특정 조건을 만족하는지를 판단하는 것이 매우 중요하다. 이로 인해 조건부 타입 분기, 타입 제거, 혹은 타입 제한을 적용할 수 있다. 이 절에서는 any, never, array, tuple, union 여부 등을 판별하는 여러 유틸리티 타입을 정의해본다.
1.1 IsNever
never 타입은 분배법칙이 작동할 경우 의도치 않게 확산될 수 있기 때문에, 이를 방지하기 위해 배열로 감싸서 판단한다.
type IsNever<T> = [T] extends [never] ? true : false;
1.2 IsAny
any 타입은 어떤 타입과도 교차될 수 있다는 특성을 이용하여 판단한다.
type IsAny<T> = string extends (number & T) ? true : false;
T가 any일 경우, number & T는 any가 되고, string extends any는 true가 되어 최종적으로 true를 반환한다.
1.3 IsArray
단순히 T extends unknown[]만으로는 never, any, readonly []를 제대로 처리하지 못한다. 아래와 같은 다단계 조건이 필요하다.
type IsArray<T> = IsNever<T> extends true
? false
: T extends readonly unknown[]
? IsAny<T> extends true
? false
: true
: false;
1.4 IsTuple
튜플은 배열과 달리 length 속성이 고정된 숫자 리터럴이다. 이를 이용해 튜플인지 여부를 판별할 수 있다.
type IsTuple<T> = IsNever<T> extends true
? false
: T extends readonly unknown[]
? number extends T['length']
? false
: true
: false;
T["length"]가 number를 포함하면 일반 배열이므로 튜플이 아니다.
1.5 IsUnion
분배법칙의 특성을 활용해 유니언 타입인지 여부를 판별할 수 있다. 이를 위해 두 번째 매개변수 U = T를 추가해 원래 타입을 저장해둔다.
type IsUnion<T, U = T> = IsNever<T> extends true
? false
: T extends T
? [U] extends [T]
? false
: true
: false;
2. 집합 연산 기반 유틸리티 타입
타입스크립트의 타입 시스템은 집합 이론의 원리를 충실히 따른다. 이를 활용해 타입 간의 차집합, 대칭차집합, 부분집합 등을 구현할 수 있다.
2.1 Diff: 차집합
두 객체 타입 A, B가 있을 때, A - B를 구하는 타입이다.
type Diff<A, B> = Omit<A & B, keyof B>;
type R1 = Diff<{ name: string; age: number }, { name: string; married: boolean }>;
// 결과: { age: number }
2.2 SymDiff: 대칭차집합
겹치지 않는 속성만 남기는 타입이다.
type SymDiff<A, B> = Omit<A & B, keyof (A | B)>;
type R2 = SymDiff<{ name: string; age: number }, { name: string; married: boolean }>;
// 결과: { age: number; married: boolean }
2.3 SymDiffUnion: 유니언 타입에서의 대칭차집합
객체가 아닌 유니언 타입에도 대칭차집합 개념을 적용할 수 있다.
type SymDiffUnion<A, B> = Exclude<A | B, A & B>;
type R3 = SymDiffUnion<1 | 2 | 3, 2 | 3 | 4>;
// 결과: 1 | 4
3. 포함 관계와 동일성 판단
3.1 IsSubset
A가 B에 포함되는 타입인지 판단한다.
type IsSubset<A, B> = A extends B ? true : false;
3.2 Equal: 타입 동일성 판단
타입스크립트에서 A가 B를 포함하고, B도 A를 포함할 때 두 타입은 동일하다.
type Equal<A, B> = [A] extends [B] ? [B] extends [A] ? true : false : false;
단, any를 정확히 구별하지 못하는 단점이 있다. 이를 극복하기 위해 다음과 같은 고급 기법이 사용된다.
type Equal2<X, Y> = (<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2)
? true
: false;
3.3 NotEqual
두 타입이 다를 경우에 true를 반환하는 타입이다.
type NotEqual<X, Y> = Equal<X, Y> extends true ? false : true;