🔤

TypeScript 제네릭 패턴 — T가 뭔지 한 번에 이해하기

함수/클래스/인터페이스에서 "나중에 결정되는 타입"을 쓰는 법

// any를 쓰면 타입 정보가 사라진다
function first(arr: any[]): any { return arr[0]; }
const x = first([1, 2, 3]);  // x의 타입: any 😱

// 제네릭을 쓰면 타입이 보존된다
function first<T>(arr: T[]): T { return arr[0]; }
const x = first([1, 2, 3]);  // x의 타입: number ✅
const y = first(['a', 'b']); // y의 타입: string ✅

T는 "호출할 때 결정된다". first([1,2,3])을 호출하면 TypeScript가 T=number로 추론한다.

자주 쓰는 패턴

제약(constraint): <T extends HasId> — T가 HasId 인터페이스를 만족해야 함

interface HasId { id: number; }
function getById<T extends HasId>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id);  // item.id 접근 가능 — T가 HasId를 만족하니까
}

keyof: <K extends keyof T> — T의 키 중 하나만 허용

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];  // 반환 타입이 정확히 해당 키의 타입
}
getProperty({ name: 'Kim', age: 30 }, 'name');  // 반환 타입: string
getProperty({ name: 'Kim', age: 30 }, 'age');   // 반환 타입: number

유틸리티 타입: Partial, Required, Pick, Omit — 전부 제네릭으로 구현되어 있다.

type Partial<T> = { [K in keyof T]?: T[K] };  // 모든 프로퍼티를 optional로

이 한 줄이 읽히면 TypeScript 제네릭을 이해한 거다.

핵심 포인트

1

T는 "아직 모르는 타입"의 자리표시자 — 호출 시점에 결정

2

T extends Constraint로 제약 추가 — T가 만족해야 하는 조건

3

keyof T로 객체의 키를 타입으로 추출

4

Partial<T>, Pick<T,K> 같은 유틸리티 타입은 제네릭 + mapped type으로 구현

사용 사례

API 클라이언트 — fetch<T>(url): Promise<T>로 응답 타입 자동 추론 컴포넌트 props — React에서 generic component 만들기