TypeScript 조건부 타입 완벽 가이드: 유니온 타입과 유틸리티 타입 활용의 모든 것
조건부 타입은 TypeScript에서 if-then-else 표현식과 같은 역할을 합니다.
결과는 두 가지 분기 중 하나로 결정되는데요.
이는 특히 제네릭 타입에서 유용하며, 유니온 타입을 다룰 때 "루프"를 돌리는 것처럼 활용할 수 있습니다.
이번 글에서는 조건부 타입의 동작 원리를 자세히 알아보겠습니다.
소스 코드에서 계산되거나 추론된 타입을 보여주기 위해 npm 패키지 asserttt를 사용합니다.
예를 들면 다음과 같습니다:
// 값의 타입 확인
assertType<string>('abc');
assertType<number>(123);
// 타입 동등성 검사
type Pair<T> = [T, T];
type _ = Assert<Equal<
Pair<string>, [string, string]
>>;
문법과 첫 번째 예제
조건부 타입의 기본 문법은 다음과 같습니다:
«Sub» extends «Super» ? «TrueBranch» : «FalseBranch»
조건부 타입은 세 부분으로 구성됩니다:
- Sub가 Super에 할당 가능한지 확인 (조건),
- 참일 경우 TrueBranch를 결과로 반환,
- 거짓일 경우 FalseBranch를 반환.
긴 조건부 타입은 다음과 같이 가독성 있게 작성하는 것을 추천합니다:
«Sub» extends «Super»
? «TrueBranch»
: «FalseBranch»
간단한 예제를 살펴보겠습니다:
type IsNumber<T> = T extends number ? true : false;
type _ = [
Assert<Equal<IsNumber<123>, true>>,
Assert<Equal<IsNumber<number>, true>>,
Assert<Equal<IsNumber<'abc'>, false>>,
];
조건부 타입 체이닝
JavaScript의 삼항 연산자처럼 TypeScript의 조건부 타입도 체이닝이 가능합니다:
type PrimitiveTypeName<T> =
T extends undefined ? 'undefined' :
T extends null ? 'null' :
T extends boolean ? 'boolean' :
T extends number ? 'number' :
T extends bigint ? 'bigint' :
T extends string ? 'string' :
never;
type _ = [
Assert<Equal<PrimitiveTypeName<123n>, 'bigint'>>,
Assert<Equal<PrimitiveTypeName<bigint>, 'bigint'>>,
];
중첩된 조건부 타입
이전 예제에서는 false 분기에 다음 조건부 타입이 중첩되었습니다.
그러나 true 분기에 중첩된 조건부 타입이 있다면 들여쓰기로 가독성을 높일 수 있습니다:
type RemoveEmptyStrings<T extends Array<string>> =
T extends [infer First extends string, ...infer Rest extends Array<string>]
? First extends ''
? RemoveEmptyStrings<Rest>
: [First, ...RemoveEmptyStrings<Rest>]
: [];
type _ = Assert<Equal<
RemoveEmptyStrings<['', 'a', '', 'b', '']>,
["a", "b"]
>>;
할당 가능성 검사 예제
조건부 타입을 사용해 할당 가능성을 검사할 수 있습니다:
type IsAssignableFrom<A, B> = B extends A ? true : false;
type _ = [
Assert<Equal<IsAssignableFrom<number, 123>, true>>,
Assert<Equal<IsAssignableFrom<number, 'abc'>, false>>,
];
.length 프로퍼티를 가진 타입만 래핑하기
다음 예제에서는 .length 프로퍼티가 숫자인 타입만 튜플로 래핑합니다:
type WrapLen<T> = T extends { length: number } ? [T] : T;
type _ = [
Assert<Equal<WrapLen<string>, [string]>>,
Assert<Equal<WrapLen<RegExp>, RegExp>>,
];
유니온 타입에 대한 조건부 타입 분배성
조건부 타입은 유니온 타입에 대해 분배적으로 동작합니다.
즉, 유니온 타입의 각 요소에 조건부 타입을 적용한 결과의 유니온을 반환합니다:
type WrapLen<T> = T extends { length: number } ? [T] : T;
type _ = Assert<Equal<
WrapLen<boolean | 'hello' | Array<number>>,
boolean | ['hello'] | [Array<number>]
>>;
분배성 제한 방법
분배성을 방지하려면 타입 변수를 배열로 래핑하면 됩니다:
type IsString2<T> = [T] extends [string] ? 'yes' : 'no';
type _ = [
Assert<Equal<IsString2<string>, 'yes'>>,
Assert<Equal<IsString2<string | number>, 'no'>>,
];
유니온 타입 필터링
never를 활용해 유니온 타입의 특정 요소를 제거할 수 있습니다:
type DropNumber<T> = T extends number ? never : T;
type _ = Assert<Equal<
DropNumber<1 | 'a' | 2 | 'b'>,
'a' | 'b'
>>;
내장 유틸리티 타입 Exclude와 Extract
TypeScript는 Exclude와 Extract를 제공합니다:
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type Union = 1 | 'a' | 2 | 'b';
type _ = [
Assert<Equal<Exclude<Union, number>, 'a' | 'b'>>,
Assert<Equal<Extract<Union, number>, 1 | 2>>,
];
조건부 타입과 infer
infer를 사용해 복합 타입의 부분을 추출할 수 있습니다:
type ElemType<Arr> = Arr extends Array<infer Elem> ? Elem : never;
type _ = Assert<Equal<
ElemType<Array<string>>, string
>>;
지연된 조건부 타입
아직 값이 결정되지 않은 타입 변수를 포함하는 조건부 타입은 지연된 상태로 남습니다:
type StringOrNumber<Kind extends 'string' | 'number'> =
Kind extends 'string' ? string : number;
function randomValue<K extends 'string' | 'number'>(kind: K) {
type Result = StringOrNumber<K>;
type _ = Assert<Equal<
Result, K extends 'string' ? string : number
>>;
}
'Javascript' 카테고리의 다른 글
Express.js 완벽 마스터하기: 초보자도 쉽게 배우는 미들웨어, next 메커니즘, 라우팅의 기초부터 활용까지 (0) | 2025.03.17 |
---|---|
TypeScript 심볼 완벽 분석: 타입 레벨에서의 심볼 활용과 고급 패턴 (0) | 2025.03.15 |
TypeScript Mapped Types 완벽 정복: 기본부터 고급 활용까지 (0) | 2025.03.13 |
TypeScript의 infer 키워드로 복합 타입에서 원하는 부분만 깔끔하게 추출하기 (0) | 2025.03.13 |
TypeScript satisfies 연산자 완벽 정리: 타입 체크의 새로운 강자 (0) | 2025.03.03 |