TypeScript 제네릭 완벽 가이드: 타입 안정성 높이기
개발

TypeScript 제네릭 완벽 가이드: 타입 안정성 높이기

TypeScript 제네릭의 개념부터 실전 활용까지. 타입 안정성을 유지하면서 재사용 가능한 코드를 작성하는 방법을 예제 중심으로 배워봅니다.

TypeScript
제네릭
타입스크립트
웹개발
프론트엔드

제네릭이란?

제네릭은 타입을 파라미터처럼 전달할 수 있게 하는 TypeScript 기능입니다. any 타입에 의존하지 않고도 유연하고 재사용 가능한 코드를 작성할 수 있습니다.

기본 문법

제네릭 없이

function getFirst(arr: number[]): number {
  return arr[0];
}

// 문자열 배열에는 사용 불가 ❌

제네릭 사용

function getFirst<T>(arr: T[]): T {
  return arr[0];
}

const num = getFirst([1, 2, 3]); // number
const str = getFirst(['a', 'b']); // string

실전 패턴 1: API 응답 타입

interface ApiResponse<T> {
  success: boolean;
  data: T;
  message: string;
}

interface User {
  id: number;
  name: string;
}

async function fetchUser(id: number): Promise<ApiResponse<User>> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

// 타입 안정성 확보 ✅
const result = await fetchUser(1);
console.log(result.data.name); // User 타입으로 자동 추론

실전 패턴 2: 유틸리티 함수

function pluck<T, K extends keyof T>(arr: T[], key: K): T[K][] {
  return arr.map(item => item[key]);
}

const users = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 },
];

const names = pluck(users, 'name'); // string[]
const ages = pluck(users, 'age');   // number[]

// 존재하지 않는 키는 에러 ❌
const invalid = pluck(users, 'salary'); // 컴파일 에러!

실전 패턴 3: React 컴포넌트

interface TableProps<T> {
  data: T[];
  columns: {
    key: keyof T;
    header: string;
  }[];
}

function Table<T>({ data, columns }: TableProps<T>) {
  return (
    <table>
      <thead>
        <tr>
          {columns.map(col => (
            <th key={String(col.key)}>{col.header}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {data.map((item, idx) => (
          <tr key={idx}>
            {columns.map(col => (
              <td key={String(col.key)}>
                {String(item[col.key])}
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

고급 패턴: 조건부 타입

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

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

흔한 실수

❌ 제네릭 제약 조건 누락

// 나쁜 예
function getProp<T>(obj: T, key: string) {
  return obj[key]; // 에러!
}

// 좋은 예
function getProp<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]; // 타입 안전 ✅
}

❌ 과도한 제네릭 사용

// 나쁜 예 (불필요하게 복잡)
function add<T extends number>(a: T, b: T): T {
  return (a + b) as T;
}

// 좋은 예 (단순)
function add(a: number, b: number): number {
  return a + b;
}

핵심 정리

제네릭은 타입 안정성코드 재사용성을 모두 제공합니다. API 응답 타입 정의, 공통 유틸리티 함수, React 컴포넌트 등에서 적극 활용하면 런타임 에러를 줄이고 생산성을 높일 수 있습니다.