[TypeScript] 재사용 가능한 컴포넌트 제네릭 만들기

제네릭 기본: 값 그대로 전달하기

제네릭의 기본은 입력받은 값을 그대로 반환하는 함수부터 시작합니다. `any`와 달리 타입 정보를 유지하면서도 모든 타입을 처리할 수 있습니다.

function reflect<T>(data: T): T {
  return data;
}

const text = reflect<string>("안녕하세요");
const num = reflect<number>(42);

console.log(text); // 출력: 안녕하세요
console.log(num);  // 출력: 42

제네릭 제약 조건: 특정 속성이 있는 타입만 허용하기

제네릭은 기본적으로 모든 타입에 대해 동작하지만, 때로는 특정 속성이 있는 타입에만 동작하도록 제한할 필요가 있습니다.

interface Measurable {
  size: number;
}

function larger<T extends Measurable>(item1: T, item2: T): T {
  return item1.size >= item2.size ? item1 : item2;
}

const str1 = { content: "짧음", size: 3 };
const str2 = { content: "길어짐", size: 5 };
console.log(larger(str1, str2).content); // 출력: 길어짐

// console.log(larger("a", "b")); // 오류: size 속성 없음

제네릭 인터페이스

제네릭 인터페이스를 사용하면, 함수뿐 아니라 객체의 호출 시그니처를 제네릭으로 정의하여 재사용할 수 있습니다.

// 두 개의 타입 T와 U를 받아서, 두 값을 묶어주는 제네릭 인터페이스
interface Pair<T, U> {
  first: T;
  second: U;
}

function makePair<T, U>(a: T, b: U): Pair<T, U> {
  return { first: a, second: b };
}

const pair1 = makePair("apple", 5);
console.log(pair1);  // 출력: { first: "apple", second: 5 }

const pair2: Pair<number, boolean> = { first: 42, second: true };
console.log(pair2);  // 출력: { first: 42, second: true }

제네릭 클래스

제네릭 클래스는 클래스의 모든 인스턴스 멤버가 같은 타입을 다루도록 보장할 때 유용합니다.

class Queue<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.shift();
  }
}

const numberQueue = new Queue<number>();
numberQueue.push(1);
numberQueue.push(2);
console.log(numberQueue.pop()); // 출력: 1

const stringQueue = new Queue<string>();
stringQueue.push("a");
console.log(stringQueue.pop()); // 출력: a