조건부 타입은 extends와 삼항 연산자를 이용해 조건에 따라 각각 다른 타입을 정의하도록 돕는 문법
type A = number extends string ? number : string;위 조건부 타입의 조건식 number extends string은 number 타입이 string 타입의 서브타입이 아니기 때문에 거짓이 되고 그 결과 타입 A는 string 타입이 됨
조건식에 객체 타입을 사용
type ObjA = {
a: number;
};
type ObjB = {
a: number;
b: number;
};
type B = ObjB extends ObjA ? number : string;→ B는 ObjB는 ObjA의 서브타입이기에 조건식이 참이되어 number 타입이됨
조건부 타입은 제네릭과 함께 사용할 때 위력이 극대화됨
다음은 타입변수에 Number 타입이 할당되면 String 타입을 반환하고 그렇지 않으면 Number 타입을 반환하는 조건부 타입임
type StringNumberSwitch<T> = T extends number ? string : number;
let varA: StringNumberSwitch<number>;
// string
let varB: StringNumberSwitch<string>;
// number- varA는 T에 number 타입을 할당함. 그 결과 조건식이 참이 되어 string 타입이 됨
- varB는 T에 string 타입을 할당함. 그 결과 조건식이 거짓이 되어 number 타입이 됨
다음은 매개변수 String 타입의 값을 제공받아 공백을 제거한 다음 반환하는 함수임
function removeSpaces(text: string) {
return text.replaceAll(" ", "");
}
let result = removeSpaces("hi im winterlood");이때 이 removeSpaces 함수의 매개변수에 undefined나 null 타입의 값들도 제공될 수 있다고 가정
그럼 다음과 같이 수정해야 함
function removeSpaces(text: string | undefined | null) {
return text.replaceAll(" ", ""); // ❌ text가 string이 아닐 수 있음
}
let result = removeSpaces("hi im winterlood");→ 하지만 text의 타입이 string이 아니면 오류발생…
따라서 다음과 같이 타입을 좁혀 사용해야 함
function removeSpaces(text: string | undefined | null) {
if (typeof text === "string") {
return text.replaceAll(" ", "");
} else {
return undefined;
}
}
let result = removeSpaces("hi im winterlood");
// 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 winterlood");
// string
let result2 = removeSpaces(undefined);
// 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;
}
}
let result = removeSpaces("hi im winterlood");
// string
let result2 = removeSpaces(undefined);
// undefined하지만 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;
}
}
let result = removeSpaces("hi im winterlood");
// string
let result2 = removeSpaces(undefined);
// undefined