조건부 타입(Conditional Type)
삼항 연산자 조건문 형태를 가지는 타입 시스템이다.
string extends string ? 'yes' : 'no'
근데 이렇게만 보면 쓸데 없어 보이는데, 제네릭과 함께한다면 이야기가 달라진다. 아래 예시를 보자.
function removeSpaces(text:string){
return text.replaceAll(' ', '');
}
let result = removeSpaces("hello world");
만약 여기에 null | undefined가 올 수 있다면 어떻게 할까?
function removeSpaces(text: string | null | undefined): string | undefined {
if (typeof text === 'string') {
return text.replaceAll(' ', '');
}
return undefined;
}
let result = removeSpaces("hello world");
이렇게 하면 문제가 해결된 것처럼 보이지만, result의 타입이 string | undefined가 돼버리는 것을 볼 수 있다. 그렇다면 제네릭을 사용해보자.
function removeSpaces<T>(text: T): T extends string ? string : undefined {
if (typeof text === "string") {
return text.replaceAll(" ", "") as any; // any를 사용하지 않을 경우, 함수 내부에서는 T의 타입을 알 수 없기 때문에 에러 발생
} else {
return undefined as any;
}
}
하지만, 타입 단언을 사용하지 않으면 에러가 발생한다. 그런데 이렇게 할 거면 처음부터 any를 쓰면 되고 any를 쓰면 그냥 js를 쓰면 되는 것과 다를 게 없다.
이때, 타입 단언 보다는 함수 오버로딩을 사용하는 것이 더 좋다.
function removeSpaces<T>(text: T): T extends string ? string : undefined;
function removeSpaces(text: unknown) {
if (typeof text === "string") {
return text.replaceAll(" ", "");
} else {
return undefined;
}
}
let result = removeSpaces("hi im winterlood");
// string
let result2 = removeSpaces(undefined);
// undefined
📌 분산적인 조건부 타입(Distributive Conditional Types)
분산적인 조건부 타입은 유니온 타입에 조건부 타입을 적용할 때, 각각의 타입에 대해 조건부 타입이 분산되어 적용되는 것을 말한다.
type StringNumberSwitch<T> = T extends number ? string : number;
type Test = StringNumberSwitch<string | number>; // 'string' | 'number'
이때 우리가 알고 있는 것은 유니온 타입이 더 크므로 false가 되어 number가 되어야 하는데, 신기하게도 결과는 'string' | 'number'가 된다. 이는 조건부 타입이 유니온 타입의 각 타입에 대해 분산되어 적용되기 때문이다.
만약, 분산적 조건부 타입을 막으려면 extends 양쪽에 대괄호를 감싸면 된다.
type NonDistributive<T> = [T] extends [number] ? string : number;
type Test2 = NonDistributive<string | number>; // number
📌 infer(추론)
조건부 타입은 infer 키워드를 사용하여 타입을 추론할 수 있다.
type Flatten<T> = T extends Array<infer I> ? I : T;
이 조건식을 참이 되도록 만들 수 있는 최적 타입을 추론하라는 의미이다. 만약 추론이 불가능한 식을 받으면 조건식을 거짓으로 판단한다. 아래 예시를 보자.
type PromiseUnpack<T> = T extends Promise<infer R> ? R : never;
type PromiseA = PromiseUnpack<Promise<number>>;
type PromiseB = PromiseUnpack<Promise<string>>;
이렇게 하면 Promise의 내부 타입을 추론할 수 있다.

