조건부 타입은 extends
와 삼항 연산자를 이용해 조건에 따라 각각 다른 타입을 정의하도록 돕는 문법이다.
type A = number extends string ? string : number;
위 조건부 타입의 조건식 number extends string
은 number
타입이 string
타입의 서브타입이 아니기 때문에 거짓이 되고 그 결과 타입 A
는 number
타입이 된다.
type ObjA = {
a: number;
};
type ObjB = {
a: number;
b: number;
};
type B = ObjB extends ObjA ? number : string;
객체 타입도 마찬가지로 ObjB
는 ObjA
의 서브 타입 이므로 조건식이 참이되어 B
는 number
타입이 된다.
조건부 타입은 제네릭과 함께 사용할 때 그 위력이 극대화 된다.
type StringNumberSwitch<T> = T extends number ? string : number;
let varA: StringNumberSwitch<number>;
// string
let varB: StringNumberSwitch<string>;
// number
실용적인 예제를 살펴보자.
다음은 매개변수로 String
타입의 값을 제공받아 공백을 제거한 다음 반환하는 함수이다.
function removeSpaces(text: string) {
return text.replaceAll(" ", "");
}
let result = removeSpaces("hi im woodstock");
이 removeSpaces
함수의 매개변수에 undefined
이나 null
타입의 값들도 제공될 수 있다고 가정해보자.
이때 함수 내부에서 text
의 타입은 String
이 아닐 수 있기 때문에 오류가 발생한다.
따라서 이런 경우 다음과 같이 타입을 좁혀 사용해야 한다.
function removeSpaces(text: string | undefined | null) {
if (typeof text === "string") {
return text.replaceAll(" ", "");
} else {
return undefined;
}
}
let result = removeSpaces("hi im woodstock");
// string | undefined
그런데 이렇게 하면 변수 result
의 타입이 아까와는 달리 string | undefined
타입으로 추론된다.
이럴 때에는 조건부 타입을 이용해 인수로 전달된 값의 타입이 String
이면 반환값 타입도 String
이고 아니라면 반환값 타입을 undefined
으로 만들어 주면된다.
function removeSpaces<T>(text: T): T extends string ? string : undefined {
if (typeof text === "string") {
return text.replaceAll(" ", ""); // ❌
} else {
return undefined; // ❌
}
}
let result = removeSpaces("hi im woodstock"); // string
let result2 = removeSpaces(undefined); // undefined
타입변수 T
를 추가하고 매개변수의 타입을 T
로 정의한 다음 반환값의 타입을 T extends string ? string : undefined
으로 수정했다.
그런데 이렇게 수정하니 2개의 return
문 모두 오류가 발생하고 있는데, 이것은 조건부 타입의 결과를 함수 내부에서 알 수 없기 때문이다.
따라서 다음과 같이 타입 단언을 이용해 반환값의 타입을 any
타입으로 단언하면 오류가 해결된다.
function removeSpaces<T>(text: T): T extends string ? string : undefined {
if (typeof text === "string") {
return text.replaceAll(" ", "") as any;
} else {
return undefined as any;
}
}
그런데 이렇게 any
로 단언하는것은 별로 좋지 못하다.
예를 들어 다음과 같이 첫번째 return
문에서 string
이 아닌 타입의 값을 반환 해도 오류를 감지하지 못하기 때문이다.
function removeSpaces<T>(text: T): T extends string ? string : undefined {
if (typeof text === "string") {
return 0 as any; // 문제 감지 못함
} else {
return undefined as any;
}
}
따라서, 이럴 때에는 타입 단언보다는 함수 오버로딩을 이용하는게 더 좋다.
오버로드 시그니쳐의 조건부 타입은 구현 시그니쳐 내부에서 추론이 가능하기 때문에 다음과 같이 오버로드 시그니쳐를 추가해 함수 오버로딩을 구현하면 된다.
function removeSpaces<T>(text: T): T extends string ? string : undefined;
function removeSpaces(text: any) {
if (typeof text === "string") {
return text.replaceAll(" ", "");
} else {
return undefined;
}
}