// 배열이 있을 때 배열의 요소 타입을 얻어내고 싶은 상황
type El<T> = T extends (infer E)[] ? E : never;
type Str = El<string[]>; // string
type NumOrBool = El<(number | boolean)[]>; // number | boolean
추론을 맡기고 싶은 부분을 infer타입 변수로 표
컨디셔널 타입에서 타입 변수는 참 부분에서만 쓸 수 있다.
타입스크립트는 많은 부분을 스스로 추론할 수 있는데, 추론하려는 부분을 infer로 만들면 된다.
매개변수, 생성자 매개변수, 반환값, 인스턴스 타입 등
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
type MyConstructorParam<T> = T extends abstract new (...args: infer P) => any ? P : never
type MyReturnType<T> = T extends (...args: any) => infer R ? R : any
type MyInstanceType<T> = T extends abstract new (...args: any) => infer R ? R : any
type P = MyParameters<(a: string, b: number) => string> // [a: string, b: number]
type R = MyReturnType<(a: string, b: number) => string> // string
type CP = MyConstructorParam<new (a: string, b: number) => {}> // [a: string, b: number]
type I = MyInstanceType<new (a: string, b: number) => {}> // {}
서로 다른 타입 변수를 여러 개 동시에 사용 가능
type MyPAndR<T> = T extends (...args: infer P) => infer R ? [P, R] : never;
type PR = MyPAndR<(a: string, b: number) => string>;//[[a: number, b: string], string]
같은 타입 변수를 여러 곳에 사용 가능
같은 이름의 타입 변수는 서로 유니온이 된다.
반대로 매개변수의 경우 인터섹션이 된다. (반공변성)
type Union<T> = T extends { a: infer U, b: infer U } ? U : never;
type Result1 = Union<{ a: 1 | 2, b: 2 | 3 }>; // 1 | 2 | 3
type Intersection<T> = T extends {
a: (pa: infer U) => void,
b: (pb: infer U) => void,
} ? U : never;
type Result2 = Intersection<{ a(pa: 1 | 2): void, b(pb: 2 | 3): void }>; // 2
매개변수에 같은 타입 변수를 선언하면 인터섹션이 된다.
유니언을 인터섹션으로 만드는 타입
U는 제너릭이자 유니언이므로 컨디셔널 타입에서 분배법칙 발생
type UnionToIntersection<U>
= (U extends any ? (p: U) => void : never) extends (p: infer I) => void
? I
: never
type Result = UnionToIntersection<{ a: number } | { b:string }>; //{a:number} & {b:string}
type Result2 = UnionToIntersection<boolean | true>; // never;