TypeScript Generics 실전 가이드
이 차이가 중요한 이유는 TypeScript가 의외로 쉽게 “도움 되는 타입”에서 “읽기 어려운 타입 퍼즐”로 넘어가기 때문입니다.
제네릭이 진짜 필요한 순간
제네릭은 호출자와 구현자 사이 관계를 유지해야 할 때 가장 유용합니다.
- 요청한 payload 타입을 그대로 반환해야 하는 fetch helper
- 엔티티 타입을 유지하는 repository 인터페이스
- 필드 이름과 값 타입이 연결되는 form 유틸리티
- 이벤트 이름에 따라 payload 타입이 달라지는 event 시스템
이런 경우 제네릭 파라미터는 단순한 자리 표시자가 아니라, 타입 간 관계를 고정하는 장치가 됩니다.
union이나 overload가 더 나은 경우
현실에서는 제네릭이 과용되는 경우도 많습니다. 다음으로 충분한 문제라면 굳이 제네릭이 필요 없을 수 있습니다.
- union
- overload
- 명시적인 별도 타입
- 중복이 조금 있어도 더 읽기 쉬운 함수
실무에서 쓸 만한 기준은 간단합니다. 타입 파라미터가 두 곳 이상을 의미 있게 연결하지 못한다면, 그 제네릭은 없어도 될 가능성이 큽니다.
예시: 의미 있는 제네릭 계약
type ApiResult<T> =
| { ok: true; data: T }
| { ok: false; message: string };
async function requestJson<T>(url: string): Promise<ApiResult<T>> {
const response = await fetch(url);
if (!response.ok) {
return { ok: false, message: response.statusText };
}
return { ok: true, data: (await response.json()) as T };
}
이 코드는 호출자가 T를 고르고, 함수가 그 타입 관계를 결과까지 유지한다는 점에서 제네릭 가치가 분명합니다.
유연성보다 제약이 더 중요하다
제네릭 품질은 대개 “얼마나 유연한가”보다 “필요한 제약을 정확히 표현하는가”에서 갈립니다.
function pluck<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
여기서 중요한 것은 T보다 K extends keyof T입니다. 이 제약이 있어야 helper가 믿을 만해집니다.
TypeScript에서 좋은 제네릭은 자유도를 늘리는 것이 아니라, 허용되는 관계를 더 정확히 표현하는 것에 가깝습니다.
타입 추론도 API 품질의 일부다
많은 개발자가 “컴파일되느냐”만 보지만, 실무에서는 추론 품질이 곧 API 품질입니다.
호출할 때마다 명시적 타입 인자, 타입 단언, 우회 코드가 필요하다면 그 제네릭은 지나치게 영리할 가능성이 큽니다.
좋은 제네릭 API는 보통:
- 기본 추론이 잘 되고
- 타입 파라미터 수가 적고
- 에러 메시지가 읽히며
- 호출 지점에서 의도가 바로 드러납니다
conditional types는 절제해서 써야 한다
infer, conditional types는 강력하지만 가독성 경사가 큽니다.
다음 조건이면 쓸 가치가 있습니다.
- 반복되는 경계 문제를 해결한다
- 결과 타입 중복을 여러 곳에서 줄여 준다
- 팀이 코드 리뷰에서 동작 원리를 설명할 수 있다
반대로 “한 줄 덜 쓰기 위해” 또는 “타입 레벨에서 멋져 보이기 위해” 쓰는 순간 유지보수 비용이 급격히 올라갑니다.
자주 보는 안티패턴
- 그냥 있어 보여서
<T>를 붙이는 경우 - 의미 약한 타입 파라미터를 여러 개 늘리는 경우
- 추론이 너무 복잡해서 에러 메시지가 읽히지 않는 경우
- 서로 다른 문제를 하나의 제네릭 추상화로 덮으려는 경우
- 런타임 불확실성을 타입 단언으로만 정교해 보이게 하는 경우
특히 마지막은 위험합니다. 타입은 신뢰를 표현할 수는 있어도, 신뢰를 만들어 주지는 못합니다.
리뷰 체크리스트
- 이 제네릭이 실제로 의미 있는 타입 관계를 보존하는가
- union, overload, 명시 타입이 더 분명하지 않은가
- 제약이 실제 규칙을 드러내는가
- 호출자가 타입 시스템과 싸우지 않고도 사용할 수 있는가
- 코드 리뷰가 타입 고고학이 되지 않고 설명 가능한가
마무리 판단
제네릭은 의도를 코드베이스 전체에 전달할 때 가장 강합니다. 반대로 평범한 API를 타입 퍼즐로 만들면 해롭습니다. 좋은 TypeScript 설계는 추상화를 극대화하는 것이 아니라, 관계는 보존하되 호출 지점은 쉽게 읽히게 만드는 것입니다.
Continue Reading
다음으로 읽기 좋은 글
TypeScript Utility Types 실전 가이드
TypeScript 유틸리티 타입을 DTO, 업데이트 payload, selector, 파생 타입 설계에 어떻게 써야 하는지, 어디서부터는 가독성을 해치는지 정리합니다.
💬 LanguageModern JavaScript ES2024 문법 실전 해설
최신 JavaScript 문법을 단순 문법 소개가 아니라, 코드 품질을 실제로 높이는지 기준으로 읽습니다. 무엇이 진짜 도움이 되고 무엇은 여전히 절제가 필요한지 정리합니다.
🔧 ToolsESLint + Prettier 설정 가이드: 실전 운영 기준
React, Vue, TypeScript 프로젝트에서 ESLint와 Prettier의 역할을 분리하고, 로컬 autofix와 CI enforcement를 팀 단위로 운영하는 기준을 정리합니다.
📱 MobilePWA 완전 가이드: 앱스토어 없이 설치 가능한 웹앱
Service Worker, Web App Manifest, 오프라인 지원, 푸시 알림, 홈 화면 설치까지 포함해 PWA를 실무 기준으로 설명합니다.
다음 탐색