TestForge | Aidevops | 📊 Plogger ✍️ Blog 📚 Docs
plogger

AI DevOps Korea

AI 서비스 개발, 운영, 성능개선을 하나의 루프로 연결합니다

aidevops.kr에서 LLMOps, RAG, AI Agent, 관측성, 평가, 비용-성능 최적화를 실전 운영 관점으로 정리합니다.

TypeScript Generics 실전 가이드

· 수정 4월 21일
TypeScript Generics 실전 가이드 다이어그램
이 글에서 다루는 핵심 흐름, 아키텍처 구조, 주요 판단 포인트를 한눈에 이해할 수 있도록 정리한 그림입니다.
TypeScript 제네릭은 흔히 "재사용 도구"로 소개되지만, 실무에서 더 중요한 역할은 값 사이의 관계를 타입 시스템 안에 보존하는 데 있습니다. 그래서 핵심 질문은 "제네릭으로 만들 수 있는가?"가 아니라, "이 제네릭 설계가 다음 사람에게 API 계약을 더 분명하게 보여주는가?"입니다.

이 차이가 중요한 이유는 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

다음으로 읽기 좋은 글

다음 탐색

이 주제를 시스템 관점으로 더 이어서 보기