FEInterview Prep

typescript · high priority

TypeScript 심화 — 조건부 · 매핑 · 템플릿 리터럴 · `infer`

타입 레벨 함수형 프로그래밍 — 분배(distributive) · key remapping · 패턴 매칭

advanced 난이도7시간토스카카오네이버배민라인
시작 전
이해도
매우 낮음

학습 개요

탄생 배경

쉬운 설명

복잡한 개념을 실생활 비유로 설명합니다.

레고 부품 공장의 *조립 라인 설계*

제네릭은 *부품 모양 일반화*, 조건부 타입은 *분기 컨베이어 (왼/오른쪽으로 보내기)*, 매핑 타입은 *배열의 모든 부품에 같은 작업 적용*, `infer` 는 *지나가는 부품에서 한 조각만 떼어내기*, 템플릿 리터럴은 *부품 라벨 합성기*. 이 다섯 도구가 한 라인에 모이면, *완성품 레고 모양만 보고도* 어떤 조립 라인을 거쳤는지 컴파일러가 *역으로* 계산해낼 수 있습니다. 라이브러리의 "타입 마법" 은 다 이 라인의 조합입니다.

핵심 개념

`infer` — 함수에서 매개변수와 반환 타입을 뽑아내기typescript
1// 직접 구현해 본 ReturnType
2type MyReturn<F> = F extends (...args: any[]) => infer R ? R : never;
3
4type R1 = MyReturn<() => string>; // string
5type R2 = MyReturn<() => Promise<User>>; // Promise<User>
6
7// Parameters 도 같은 패턴
8type MyParams<F> = F extends (...args: infer P) => any ? P : never;
9type P1 = MyParams<(a: string, b: number) => void>; // [string, number]
10
11// 중첩 — Promise 안의 타입까지
12type Awaited2<T> = T extends Promise<infer U> ? Awaited2<U> : T;
13type X = Awaited2<Promise<Promise<string>>>; // string

`infer` 위치가 곧 *어디를 잡을지* 결정

infer 는 *조건부 타입의 extends 절 안* 에서만 쓸 수 있습니다. 위치를 함수 매개변수 자리에 두면 매개변수, 반환 위치에 두면 반환 타입을 잡습니다. *위치 = 의미*.

먼저 출력 순서를 예측해보세요
예측 문제typescript
1type First<T> = T extends [infer F, ...any[]] ? F : never;
2type Last<T> = T extends [...any[], infer L] ? L : never;
3
4type A = First<[1, 2, 3]>; // ?
5type B = Last<[1, 2, 3]>; // ?
6type C = First<[]>; // ?
7type D = First<string[]>; // ? (튜플이 아니라 배열)

정답

A = 1, B = 3, C = never, D = string (배열은 길이 불명이지만 첫 요소 타입은 string).

해설

튜플 패턴 매칭은 *고정 길이 + 위치* 가 명확할 때 가장 강력. 빈 튜플은 extends [infer F, ...] 에 매칭되지 않아 never. 일반 배열은 *길이 0 가능성* 때문에 마지막 요소 추출에 더 약하다 (Last<string[]>string 또는 undefined 가 되도록 더 정교하게 짜야 함).

흔한 오답

  • 튜플과 배열을 같은 추론으로 가정 — 동작이 다름.
  • Last<readonly [...]> 처럼 readonly 까지 다루려면 별도 분기 필요.

실무 적용

어떤 상황에서 사용하는가

내부 디자인 시스템에서 *컴포넌트 prop 변환 유틸리티* 를 작성한다. `Button` 의 prop 중 일부를 컨테이너가 주입하므로 *주입된 키들을 제외한 나머지만 노출* 해야 하고, 이벤트 핸들러는 *항상 옵셔널* 로 변환되어야 한다.

어떻게 적용하는가

(1) 핵심 유틸리티는 직접 구현 — `Omit<P, InjectedKeys> & Partial<EventProps<P>>` 형태. (2) `EventProps<P>` 는 `as` key remapping + 템플릿 리터럴로 `${string}Click` / `${string}Change` 같은 키만 추출. (3) Omit/Partial 의 분배 동작이 의도와 어긋나는지 *튜플로 감싸* `[K] extends [U]` 형태로 차단할지 검토. (4) DeepPartial 패턴으로 *기본값 머지* 의 타입 안전. (5) `satisfies` 로 컴포넌트 props 객체의 *리터럴 추론* 보존, IDE 자동완성을 살림. (6) 컴파일러 에러가 *Type instantiation is excessively deep* 이면 재귀 종료 조건을 단축하거나 경로 깊이를 *유한* 으로 제한.

흔한 실수와 안티패턴

  • 분배 조건부 타입을 모르고 작성 → union 일부가 사라지거나 추가됨.
  • `never` 가 분배를 거치면 결과도 `never` — DeepPartial 등에서 빈 결과 반환 버그.
  • `as` key remapping 의 `K` 가 `string | number | symbol` — 템플릿 리터럴 직전에 `string & K` 로 좁히지 않으면 에러.
  • `--strictFunctionTypes` 와 *메서드 vs 함수 프로퍼티* 의 변성 차이를 잊고 콜백 호환성 디버깅에서 고생.
  • 재귀 깊이 한계에 부딪혀 *임의의 깊은 객체* 처리에 실패 — 경로 깊이 제한 필요.

흔한 오해

오해

"`T extends U ? X : Y` 는 그냥 if 처럼 *한 번* 평가된다."

교정

*벗겨낸 제네릭* 이 좌측이고 *유니온* 이 들어오면 *각 멤버에 분배* 되어 평가된다. 이걸 막으려면 `[T] extends [U]` 로 감싼다.

왜 중요

Exclude/Extract/NonNullable 의 동작 근거가 분배. 모르면 union 한 항이 사라지는 *마법* 으로 보인다.

오해

"`infer` 는 함수에서만 쓴다."

교정

튜플 (`[infer F, ...]`), 배열 (`(infer U)[]`), Promise (`Promise<infer R>`), 템플릿 리터럴 (`\`/${infer P}\``) 등 *어떤 패턴* 에서도 쓸 수 있다.

왜 중요

`infer` 는 *조건부 타입의 패턴 매칭* 도구라, 매칭 가능한 모든 위치에서 쓸 수 있다.

오해

"매핑 타입의 키는 그대로 유지된다."

교정

`as` 절로 *키를 변형* 하거나 `never` 로 보내 *제거* 할 수 있다 (4.1+).

왜 중요

getter 자동 생성, 동적 Omit, 이벤트 핸들러 키 추출 등 라이브러리 패턴의 핵심.

면접 질문

심화토스카카오네이버

답변 방향 힌트

"naked 제네릭", "각 멤버에 분배" 가 키워드.

반드시 언급할 키워드

  • `Exclude<T, U> = T extends U ? never : T`
  • *벗겨낸* 제네릭 좌측 + *유니온* 입력 → 분배 발동
  • 각 멤버에 평가: `"a" extends "a" ? never : "a"` = never, 나머지는 자기 자신
  • union 결과: `never | "b" | "c"` = `"b" | "c"`
  • `never` 가 결과에서 흡수됨
  • 분배를 막으려면 `[T] extends [U]`

예상 꼬리 질문

  • `ToArray<never>` 가 왜 `never` 가 되는지 같은 메커니즘으로 설명해 주세요.
  • `Exclude` 와 `Extract` 의 *차이가 한 글자* 인 이유는?

자기 점검

*벗겨낸 제네릭* 이 무엇이며, 분배가 발동하는 *두 조건* 은?

기대 키워드

nakedunionextends 좌측분배 차단 = `[T]`

자주 하는 오해

"조건부 타입은 항상 분배된다" — naked 가 아니거나 union 이 아니면 분배 안 됨.

`as` key remapping 의 `as` 분기에서 `never` 를 반환하면 어떤 일이 벌어지나?

기대 키워드

키 제거동적 Omit4.1+

자주 하는 오해

"키가 never 라는 이름으로 남는다" — 아예 *결과 객체에서 사라진다*.

`(x: Animal) => void` 와 `(x: Dog) => void` 중 어느 쪽이 어느 쪽에 *대입 가능* 한가? 변성 단어로 답하라.

기대 키워드

반공변매개변수더 너그러운 함수

자주 하는 오해

"Dog 가 더 구체적이니 Dog 함수가 어디든 들어간다" — 매개변수는 *반공변* 이라 반대다.

학습 자료