일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 리터럴 타입
- 무한 스크롤
- app router
- 인터섹션
- async/await
- 공변성
- 반공변성
- SSR
- Promise
- 투포인터
- ESlint
- CORS
- 타입 좁히기
- recoil
- CI/CD
- 결정 알고리즘
- 이분 검색
- TS
- 호이스팅
- dfs
- 태그된 유니온
- Cypress
- tailwind
- autosize
- RTK Query
- React
- webpack
- map
- Jest
- useAppDispatch
- Today
- Total
짧은코딩
infer로 타입 추론(with 컨디셔널 타입) 본문
infer란?
infer로 타입 추론을 극한으로 활용할 수 있다.
컨디셔널 타입과 같이 사용할 수 있다.
컨디셔널 타입(Conditional Type)이란?
type A1 = string;
type B1 = A1 extends string ? number : boolean; // number
type A2 = number;
type B2 = A2 extends string ? number : boolean; // boolean
특정 타입 extends 다른 타입 ? 참일 때 타입 : 거짓일 때 타입
특정 타입이 다른 타입의 부분집합일 때 참이 된다.
infer의 예시들
배열의 요소 타입을 얻고 싶은 경우
type El<T> = T extends (infer E) [] ? E : never;
type Str = El<string[]>; // string
type NumOrBool = El<(number | boolean)[]>; // number | boolean
여기서는 E가 타입 변수이다.
타입 추론을 맡기고 싶은 부분을 "infer {타입 변수}"로 표시하면 된다.
유의할 점으로는 컨디셔널 타입에서 타입 변수는 참 부분에서만 쓸 수 있다. 거짓 부분에서 사용하면 에러가 발생한다.
매개변수, 생성자 매개변수, 반환값, 인스턴스 타입을 추론하는 경우
// (...args: any) => any는 임의의 함수를 타이핑 하는 부분
type MyParaneters<T> = T extends (...args: infer P) => any ? P : never;
type P = MyParaneters<(a: string, b: number) => string>; // [a: string, b: number]
// abstract new (...args: any) => any는 임의의 생성자를 타이핑하는 부분
type MyConstructorParameters<T> = T extends abstract new (...args: infer P) => any ? P : never;
type CP = MyConstructorParameters<new (a: string, b: number) => {}>; // [a: string, b: number]
type MyReturnType<T> = T extends (...args: any) => infer R ? R : any;
type R = MyReturnType<(a: string, b: number) => string>; // string
type MyInstanceType<T> = T extends abstract new (...args: any) => infer R ? R : any;
type I = MyInstanceType<new (a: string, b: number) => {}> // {}
여기서 타입 P와 CP는 [a: string, b: number]로 표시가 된다.
"[string, number]로 표시되어야 하는 것이 아닌가?"하는 의문을 가질 수 있다. 결론부터 말하면 [string, number]와 같은 의미이며, 튜플의 각 자리에 이름을 붙인 것이다.
서로 다른 타입 변수를 여러 개 사용하기
type MyPAndR<T> = T extends (...args: infer P) => infer R ? [P, R] : never;
type PR = MyPAndR<(a: string, b: number) => string>; // [[a: string, b: number], string]
매개변수는 P 타입 변수로, 반환값은 R 타입 변수로 추론했다.
같은 타입 변수를 여러 개 사용하기
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
Result1은 Union 타입을 사용하여 1 | 2 | 3이 되었다.
하지만 Result2는 좀 다르게 봐야한다. 매개변수는 반공변성을 갖기 때문에 매개변수에서는 인터섹션이 된다. 따라서 2가 된다.
타입 변수 중에서 하나가 매개변수고, 하나가 반환값인 경우(실무에서는 거의 못 봄)
type ReturnAndParam<T> = T extends {
a: () => infer U,
b: (pb: infer U) => void
} ? U : never;
type Result3 = ReturnAndParam<{a: () => 1 | 2, b(pb: 1 | 2 | 3): void}>; // 1 | 2
type Result4 = ReturnAndParam<{a: () => 1 | 2, b(pb: 2 | 3): void}>; // never
코드만 보면 어떤 규칙으로 결과가 나오는지 헷갈린다.
반환값의 타입이 매개변수의 타입의 부분집합인 경우에만 그 둘의 교집합이된다. 그 외에는 모두 never가 된다.
유니언을 인터섹션으로 만드는 타입
이 방법에서는 매개변수에 같은 타입 변수를 선언하면 인터섹션이 된다는 가정이 있어야 한다.
type UnionToIntersection<U>
= (U extends any ? (P: U) => void : never) extends (p: infer I) => void
? I
: never;
type Result5 = UnionToIntersection<{a: number} | {b: string}>; // {a: number;} & {b: string;}
type Result6 = UnionToIntersection<boolean | true>; // never
U는 제네릭이자 유니언이라서 컨디셔널 타입에서 분배법칙이 실행된다.
-Result5
Result5는 UnionToIntersection<{a: number}> | UnionToIntersection<{b: number}>이 된다.
그렇기에 UnionToIntersection<{a: number}>는 U extends any ? (p: U) => void : never)에서 (p: {a: never}) => void로 바뀐다. 이 타입은 (p: infer I) => void니까 I는 {a: never}이 된다.
이 규칙을 UnionToIntersection<{b: number}에도 적용하면 {b: string}이 된다.
결과적으로 {a: number} & {b: string}이 되고, 이는 유니언을 인터섹션으로 바꾼 셈이 된다.
-Result6
Result6는 UnionToIntersection<boolean | true>가 UnionToIntersection<true | false | true>가 된다.
그리고 true & false & true는 never라서 never가 된다.
'TS > TS(with ZeroCho)' 카테고리의 다른 글
재귀 타입 & 템플릿 리터럴 타입 (0) | 2023.11.17 |
---|---|
브랜드 속성 & 타입 좁히기 (1) | 2023.11.12 |
공변성과 반공변성(함수) (1) | 2023.10.02 |
오버로딩(any를 써야하는 경우!) (0) | 2023.09.28 |
타입을 집합으로 생각하자 (0) | 2023.09.15 |