Typescript 제네릭, infer

created:
last-updated:

TypeScript는 정적 타입 언어로서 코드의 안정성과 예측 가능성을 높이는 데 큰 장점이 있다. 그중에서도 제네릭(Generic)과 조건부 타입, 그리고 infer 키워드는 타입 시스템을 훨씬 더 강력하게 만들어주는 도구다.

이번 글에서는 제네릭의 기본 개념부터 시작해, infer 키워드가 어떤 맥락에서 등장하고, 어떻게 활용되는지를 예제 중심으로 정리해본다. 제네릭과 infer 키워드를 대충 생각만 하고 코딩하다가 막상 정확히 설명하려니 잘 설명을 못하겠더라.. 이번 기회에 정리!


제네릭(Generic)이란?

제네릭은 타입을 파라미터화하는 문법이다. 즉, 값이 아니라 타입을 함수나 클래스, 인터페이스 등에서 동적으로 받을 수 있도록 해준다. Java나 C++과 같은 언어에서도 존재하는 개념이며, TypeScript에서도 핵심적인 타입 설계 도구다.


function identity<T>(value: T): T {
  return value;
}

const a = identity<string>("hello"); // string
const b = identity<number>(123);     // number

위의 identity 함수는 인자로 받은 값을 그대로 반환한다. 중요한 점은 T라는 타입 변수(type variable)를 사용해 입력값의 타입을 그대로 반환값에도 사용한다는 것이다. 이 덕분에 identity 함수는 어떤 타입이 오더라도 타입 안정성을 유지할 수 있다.

제네릭의 장점

제네릭과 조건부 타입의 만남

제네릭은 다른 고급 타입과 조합되면 훨씬 더 유용하다. 예를 들어 조건부 타입(Conditional Types)을 통해 입력 타입에 따라 결과 타입을 달리할 수 있다.

type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">; // true
type B = IsString<123>;     // false

이런 조건부 타입이 강력해지는 순간은 타입의 일부를 추론해야 할 때이다. 이때 등장하는 것이 바로 infer이다.

infer란 무엇인가?

infer는 조건부 타입 안에서 타입을 추론해 새로운 타입 변수로 사용하는 키워드이다. 특정 구조의 타입에서 내부 타입을 꺼내 사용하고자 할 때 매우 유용하다.

기본 문법

type Unwrap<T> = T extends Promise<infer U> ? U : T;

이 예제는 Promise<string>과 같은 타입에서 string을 추출한다. 만약 Promise가 아니라면 원래 타입을 그대로 반환한다.

type A = Unwrap<Promise<number>>; // number
type B = Unwrap<string>;          // string

자주 쓰이는 infer 예제

1. 배열 요소 타입 추출

type ElementType<T> = T extends (infer U)[] ? U : never;

type A = ElementType<string[]>; // string
type B = ElementType<boolean>;  // never

infer U를 통해 배열 요소 타입을 추론하고 있다. 배열이 아닌 경우에는 never을 반환한다.

2. 함수 반환 타입 추출

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type A = MyReturnType<() => number>; // number
type B = MyReturnType<(x: string) => void>; // void

함수의 반환 타입을 추론할 수 있다. 실제로 TypeScript의 내장 유틸리티 타입 ReturnType<T>도 이런 방식으로 구현되어 있다.

3. 튜플 첫 요소 추출

type Head<T> = T extends [infer H, ...unknown[]] ? H : never;

type A = Head<[1, 2, 3]>; // 1
type B = Head<[]>;        // never

튜플 구조 분해를 통해 첫 번째 요소만 뽑아낼 수 있다. infer는 배열이나 튜플과 같은 구조에서도 강력하게 작동한다.

정리

infer는 TypeScript의 조건부 타입(Conditional Types) 안에서 사용되는 키워드로, 타입 추론(type inference) 을 가능하게 해준다. 쉽게 말해, 어떤 타입으로부터 부분 타입을 추출해 이름을 붙여서 나중에 그 이름으로 활용할 수 있게 해주는 기능이다.