본문 바로가기
C.W.K.
Stream
Lesson 06 of 06 · published

`infer` 심화: 타입 구조에서 필요한 조각 뽑기

~11 min · type-manipulation, infer, advanced, type-extraction

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"infer는 타입을 추측하는 마법이 아니라, 패턴이 맞을 때 그 자리를 이름 붙이는 문법이야."

조건부 타입 안의 포획 변수

T extends Array<infer E> ? E : T에서 infer ET가 배열 패턴에 맞을 때 원소 자리를 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로 감싼 값을 얻어. 표준 유틸리티인 ReturnTypeAwaited도 같은 계열의 생각을 사용해.

템플릿 리터럴 타입에서는 T extends `${infer Head}-${infer Tail}` ? [Head, Tail] : never처럼 구분자 앞뒤를 포획할 수 있어. 다만 구분자가 여러 번 등장할 때 어떤 부분이 잡히는지까지 API 계약으로 기대하려면 테스트와 명확한 제한이 필요해.

트랙 8에서 배열 원소, 함수 반환, Promise 값, 첫 번째 매개변수를 꺼내는 네 패턴을 배웠어. 여기서는 같은 infer를 한 패턴에 여러 번 쓰고, 재귀 조건부 타입과 템플릿 리터럴 패턴까지 결합해 라이브러리 타입을 읽는 단계로 확장해.

infer는 구조와 이름 사이의 연결점이야. 제네릭, 조건부 타입, 매핑된 타입, 템플릿 리터럴 타입을 한꺼번에 쓰더라도 실제 문제에서 필요한 관계만 남겨 읽을 수 있어야 해.

Code

고급 infer 패턴 — 여러, 재귀, template literal·typescript
// 여러 infer — tuple destructuring.
type FirstTwo<T> = T extends [infer A, infer B, ...unknown[]] ? [A, B] : never;
type A = FirstTwo<[1, 2, 3, 4]>;       // [1, 2]

// 재귀 — 중첩 배열 flatten.
type Flatten<T> = T extends (infer E)[] ? Flatten<E> : T;
type B = Flatten<string[][][]>;        // string
type C = Flatten<number>;              // number (base case)

// 재귀 — deep readonly.
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

// Template literal + infer — string 타입 split.
type Split<S extends string, D extends string> =
  S extends `${infer Head}${D}${infer Tail}`
    ? [Head, ...Split<Tail, D>]
    : [S];

type D = Split<'a-b-c-d', '-'>;        // ['a', 'b', 'c', 'd']

External links

Exercise

문자열을 타입 수준에서 뒤집는 Reverse<T>를 작성해. 템플릿 리터럴의 infer로 한 글자씩 분리하고 빈 문자열에서 재귀를 끝내.
Hint
문자열에서 한 글자씩 떼어 재귀 호출해. 빈 문자열에 도달하면 재귀를 끝내.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.