"infer는 타입을 추측하는 마법이 아니라, 패턴이 맞을 때 그 자리를 이름 붙이는 문법이야."
조건부 타입 안의 포획 변수
T extends Array<infer E> ? E : T에서 infer E는 T가 배열 패턴에 맞을 때 원소 자리를 E라는 이름으로 붙잡아. 배열이 아니면 거짓 분기로 가므로 E는 그 밖에서 존재하지 않아. infer는 반드시 조건부 타입의 extends 패턴 안에서 읽어야 해.
같은 패턴에 여러 변수를 둘 수도 있어. T extends [infer First, infer Second, ...infer Rest] ? [First, Second, Rest] : never는 튜플을 앞의 두 자리와 나머지로 나눠. 값의 구조 분해와 닮았지만 모든 일은 타입 검사 때 끝나.
재귀로 중첩 벗기기
type DeepElement<T> = T extends readonly (infer E)[] ? DeepElement<E> : T는 배열이 나오는 동안 원소 타입을 다시 자기 자신에게 넘겨. DeepElement<string[][][]>의 결과는 string이야. 기본 분기인 T가 재귀를 멈추는 출구가 된다.
재귀 타입은 표현력이 크지만 깊고 넓은 합집합을 만나면 타입 계산 비용도 커져. 에디터가 느려지거나 오류가 지나치게 길어진다면 한 번에 모든 경우를 해결하려 하지 말고 입력 범위를 좁혀. 타입 체조의 완성도보다 개발 흐름이 더 중요해.
함수와 문자열 패턴 읽기
함수에서는 T extends (...args: any[]) => infer R ? R : never로 반환 타입을, Promise에서는 T extends Promise<infer U> ? U : T로 감싼 값을 얻어. 표준 유틸리티인 ReturnType과 Awaited도 같은 계열의 생각을 사용해.
템플릿 리터럴 타입에서는 T extends `${infer Head}-${infer Tail}` ? [Head, Tail] : never처럼 구분자 앞뒤를 포획할 수 있어. 다만 구분자가 여러 번 등장할 때 어떤 부분이 잡히는지까지 API 계약으로 기대하려면 테스트와 명확한 제한이 필요해.
트랙 8에서 배열 원소, 함수 반환, Promise 값, 첫 번째 매개변수를 꺼내는 네 패턴을 배웠어. 여기서는 같은 infer를 한 패턴에 여러 번 쓰고, 재귀 조건부 타입과 템플릿 리터럴 패턴까지 결합해 라이브러리 타입을 읽는 단계로 확장해.
infer는 구조와 이름 사이의 연결점이야. 제네릭, 조건부 타입, 매핑된 타입, 템플릿 리터럴 타입을 한꺼번에 쓰더라도 실제 문제에서 필요한 관계만 남겨 읽을 수 있어야 해.