
⇒ 삼항 연산자에 따라 타입을 결정하는 문법
예시1
// 예시
type A = number extends string ? string : number;
예시2
// 예시
type ObjA = {
a: number;
};
type ObjB = {
a: number;
b: number;
};
type B = ObjB extends ObjA ? number : string;
// -> A가 슈퍼타입, 조건이 참이되어 B는 number 타입이 된다.
type StringnumberSwitch<T> = T extends number ? string : number;
// T가 number면 string, string이면 number 타입으로 할당 된다.
let varA: StringnumberSwitch<number>; // string 타입
let varB: StringnumberSwitch<string>; // number 타입
에러 발생 코드
function removeSpaces(text: string | undefined | null) {
if (typeof text === "string") return text.replaceAll(" ", "");
else return undefined;
}
let result = removeSpaces("hi im jeno");
result.toUpperCase(); // string or undefined가 될 수 있으므로 에러 발생
에러 해결
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 jeno"); // string으로 추론되며 오류 해결
result.toUpperCase();
let result2 = removeSpaces(undefined);
type StringnumberSwitch<T> = T extends number ? string : number;
let c: StringnumberSwitch<number | string>;
// 유니온 타입을 할당하면 분산적인 조건부 타입으로 업그레이드됌
// 한꺼번에 두 타입이 들어가는게 아니라, 한번은 number, 한번은 stirng으로
// 분리되어서 들어가게 된다.
// StringnumberSwitch<number> -> string타입
// StringnumberSwitch<string> -> number타입
// 분리된 결과들을 각각 유니온으로 묶어준다. => string | number 타입으로 된다.
let d: StringnumberSwitch<boolean | number | string>;
// 1단계
// StringnumberSwitch<boolean>
// StringnumberSwitch<number>
// StringnumberSwitch<string>
// 2단계
// number |
// string |
// number
// 결과
// number | string으로 나오게 됌
실용적인 예제
type Exclude<T, U> = T extends U ? never : T;
// 타입 변수 T가 U의 서브타입이라면 never, 아니면 T
type A = Exclude<number | string | boolean, string>;
// 1단계
// Exclude<number, string> |
// Exclude<string, string> |
// Exclude<boolean, string>
// 2단계
// number |
// never |
// boolean
// 결과
// number | never | boolean
// 유니온 타입에 never가 있으면 사라진다.
// 유니온 타입은 타입들간의 합집합 타입을 만드는 것, never타입은 공집합 타입
// 공집합과 다른 집합을 합집합 한다는 것은 결국 원본 => number | never => number
// 따라서, A는 number | boolean 타입
예시
type Extract<T, U> = T extends U ? T : never;
type B = Extract<number | string | boolean, string>;
// 1단계
// Extract<number, string>
// Extract<string, string>
// Extract<boolean, string>
// 2단계
// never
// string
// never
⇒ infer → inference(추론)
type FuncA = () => string;
type FuncB = () => number;
type ReturnType<T> = T extends () => infer R ? R : never;
type A = ReturnType<FuncA>; // string 타입
// infer R은 R로 보면 된다. -> 타입 변수로
// infer R타입은 () => string을 참으로 만드는 타입을 추론하도록 동작함
// () => string이 () => R의 서브타입이 되려면 R타입은 string 이 되서 R이 string이 된다.
type B = ReturnType<FuncB>; // number 타입
// type B 또한 A와 같이 동작한다.
type C = ReturnType<number>; // never 타입
// () => infer R은 number 타입의 슈퍼타입이 될 수 없다. 추론 불가
예제
type PromiseUnpack<T> = T extends Promise<infer R> ? R : never;
// 1. T는 프로미스 타입이어야 한다.
// 2. 프로미스 타입의 결과값 타입을 반환해야 한다.
type PromiseA = PromiseUnpack<Promise<number>>;
// number타입
type PromiseB = PromiseUnpack<Promise<string>>;
// string타입