네, 9강에 오신 것을 진심으로 환영합니다! 🎉 많은 분들이 타입스크립트를 공부하다가 "아, 제네릭(Generic)이 도대체 뭐야... <T> 이거 나오니까 머리가 핑 도네" 하고 책을 덮는 구간입니다.

하지만 걱정 마세요. 저와 함께라면 아주 쉽게 넘을 수 있습니다. 제네릭은 사실 우리가 이미 알고 있는 **'함수 파라미터'**와 똑같은 원리거든요.


🚀 제9강. 만능 열쇠, 제네릭(Generics) 기초: "타입도 변수처럼 넘겨주자"

1. 왜 필요한가요? (문제점 인식)

우리가 어떤 데이터를 받아서 그대로 다시 뱉어주는 아주 단순한 함수를 만든다고 가정해 봅시다.

TypeScript
 
// 숫자 전용 함수: 숫자를 잘 처리하지만 문자는 못 받음
function echoNumber(message: number): number {
  return message;
}

// 문자 전용 함수: 문자는 잘 처리하지만 숫자는 못 받음
function echoString(message: string): string {
  return message;
}

데이터 타입 하나 바뀐다고 똑같은 로직의 함수를 계속 새로 만들어야 할까요? 너무 비효율적이죠. 그렇다고 **any**를 쓰자니 타입 체크를 포기하는 꼴이 됩니다.

이때 필요한 게 바로 제네릭입니다. "타입을 미리 정하지 말고, 함수를 쓸 때 그때그때 정해서 알려줄게!" 하는 방식입니다.

2. 개념 쏙쏙: "테이크아웃 컵" 비유 🥤

💡 비유: 카페의 투명한 테이크아웃 컵을 생각해 보세요.

  • 공장에서 컵을 만들 때는 내용물을 정하지 않습니다. 그냥 **'담을 수 있는 용기'**만 만듭니다.
  • 손님이 주문할 때 비로소 결정됩니다.
    • 커피를 담으면 -> 커피 컵이 되고,
    • 오렌지 주스를 담으면 -> 주스 컵이 됩니다.

제네릭이 바로 이 **'빈 컵'**입니다.

3. 문법 뽀개기 (<T>)

자, 이제 그 무섭다는 꺽쇠 괄호 < >를 정복해 봅시다.

  • () 괄호 안에는 **값(데이터)**을 변수로 받죠?
  • < > 괄호 안에는 타입을 변수로 받습니다. 보통 Type의 약자인 **T**를 많이 씁니다.
TypeScript
 
// 1. 함수 이름 뒤에 <T>를 붙여서 "이 함수는 제네릭(빈 컵)이야"라고 선언합니다.
// 2. 매개변수의 타입과 반환 타입 자리에 T를 적습니다.
function echo<T>(message: T): T {
  return message;
}

// 사용법 1: "나 지금 <string> 담을 거야!"라고 명시하기
const result1 = echo<string>("안녕하세요"); 
// 이제 result1은 string 타입이 됩니다.

// 사용법 2: "나 지금 <number> 담을 거야!"
const result2 = echo<number>(123);
// 이제 result2는 number 타입이 됩니다.

보세요! 함수 하나(echo)로 문자열도 처리하고 숫자도 처리했죠? 그런데도 any와 달리 타입은 정확하게 지켜집니다.

  • result1에 숫자를 넣으려 하거나, result1.toFixed()(숫자 함수)를 쓰면 에러가 납니다. 왜냐? <string>으로 주문했으니까요!

4. 타입스크립트의 센스 (타입 추론)

사실 실무에서는 <number>, <string> 이렇게 매번 적지 않아도 됩니다. 타입스크립트가 똑똑해서 "어? 들어가는 값이 숫자(100)네? 그럼 T는 number겠구나!" 하고 알아서 눈치챕니다.

TypeScript
 
// <number>를 생략해도 됩니다.
const result3 = echo(100); // TS: "음, T는 number군."

// <string>을 생략해도 됩니다.
const result4 = echo("반갑다"); // TS: "음, T는 string이군."

그래서 코드가 아주 깔끔해집니다.


✍️ 9강 미니 퀴즈 & 실습

제네릭, 생각보다 별거 아니죠? 그냥 **'타입을 위한 변수'**입니다.

문제 1. (개념 확인) 다음 중 제네릭을 사용하는 이유로 가장 적절한 것은?

  1. 타입 체크를 아예 하지 않기 위해 (any처럼 쓰려고)
  2. 하나의 코드로 여러 타입을 안전하게 처리하기 위해 (코드 재사용성)
  3. 함수의 실행 속도를 빠르게 하기 위해

문제 2. (코드 완성) 배열을 받아서 배열의 길이를 반환하는 것이 아니라, 배열의 첫 번째 요소를 반환하는 제네릭 함수 getFirst를 만들고 싶습니다. 빈칸을 채워보세요.

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

const num = getFirst<number>([1, 2, 3]); // 1이 나옴
const str = getFirst<string>(["a", "b", "c"]); // "a"가 나옴

**(A)**에 들어갈 코드는 무엇일까요?

  1. any
  2. number
  3. T

✅ 정답 및 해설

  • 문제 1 정답: 2번
    • 해설: 제네릭의 핵심은 재사용성안전성 두 마리 토끼를 다 잡는 것입니다. any를 쓰면 안전성을 잃지만, 제네릭은 타입을 지켜줍니다.
  • 문제 2 정답: 3번 (T)
    • 해설:
      • arr: T[]: T 타입의 요소들이 들어있는 배열을 받았습니다. (예: 숫자 배열)
      • return arr[0]: 그중 하나를 꺼내면 당연히 그 타입은 T겠죠? (예: 숫자)
      • 그래서 반환 타입(출구)도 T가 되어야 합니다.

고생하셨습니다! 👏 이제 여러분은 <T>를 보고도 겁먹지 않는 수준이 되었습니다. "아, 저 자리에 내가 원하는 타입을 끼워 넣으면 되는구나!"라고 생각하시면 됩니다.

하지만 제네릭이 진짜 빛을 발하는 순간은 API 통신을 할 때입니다. 서버에서 어떤 데이터가 올지 모를 때 제네릭이 구세주가 되어줍니다.

다음 10강에서는 이 제네릭을 실제 인터페이스와 함수에 활용하는 실전 테크닉을 배워보겠습니다. Part 2의 마지막 관문입니다!

+ Recent posts