본문 바로가기
Dev/TypeScript

타입스크립트 제네릭

by ZEROGOON 2024. 12. 12.

타입스크립트 제네릭은 다양한 데이터 유형을 처리할 수 있는 유연한 함수, 클래스, 인터페이스를 만드는 방법입니다. 제네릭을 사용하면 코드를 더욱 재사용 가능하고 유지 보수하기 쉽게 만들 수 있습니다.

제네릭 함수

제네릭 함수는 다양한 유형의 인수를 받아서 동일한 작업을 수행하는 함수입니다. 제네릭 함수를 사용하면 코드를 더욱 유연하고 재사용 가능하게 만들 수 있습니다.

function identity<T>(value: T): T {
  return value;
}

const result = identity<number>(10); // 10
const result2 = identity<string>("Hello"); // "Hello"

위의 예시에서 identity 함수는 어떤 유형의 값이든 받아서 그대로 반환합니다. 이 함수는 숫자, 문자열, 객체 등 어떤 유형의 값이든 사용할 수 있습니다.

제네릭 클래스

제네릭 클래스는 다양한 유형의 데이터를 저장할 수 있는 클래스입니다. 제네릭 클래스를 사용하면 코드를 더욱 유연하고 재사용 가능하게 만들 수 있습니다.

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

  enqueue(item: T) {
    this.data.push(item);
  }

  dequeue() {
    return this.data.shift();
  }
}

const queue = new Queue<number>();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
console.log(queue.dequeue()); // 1
console.log(queue.dequeue()); // 2
console.log(queue.dequeue()); // 3

위의 예시에서 Queue 클래스는 어떤 유형의 데이터든 저장할 수 있는 큐를 구현합니다. 이 클래스는 숫자, 문자열, 객체 등 어떤 유형의 데이터든 사용할 수 있습니다.

제네릭 인터페이스

제네릭 인터페이스는 다양한 유형의 데이터를 처리할 수 있는 인터페이스입니다. 제네릭 인터페이스를 사용하면 코드를 더욱 유연하고 재사용 가능하게 만들 수 있습니다.

interface Comparable<T> {
  compareTo(other: T): number;
}

class Number implements Comparable<number> {
  constructor(public value: number) {}

  compareTo(other: number) {
    return this.value - other.value;
  }
}

class String implements Comparable<string> {
  constructor(public value: string) {}

  compareTo(other: string) {
    return this.value.localeCompare(other.value);
  }
}

function sort(array: Comparable<T>[]): T[] {
  return array.sort((a, b) => a.compareTo(b));
}

const numbers = [new Number(1), new Number(3), new Number(2)];
const strings = ["a", "c", "b"];
console.log(sort(numbers)); // [1, 2, 3]
console.log(sort(strings)); // ["a", "b", "c"]

위의 예시에서 Comparable 인터페이스는 compareTo 메서드를 정의합니다. Number 클래스와 String 클래스는 Comparable 인터페이스를 구현합니다. sort 함수는 Comparable 인터페이스를 구현한 배열을 정렬합니다.

제네릭 제약

제네릭 제약은 제네릭 함수, 클래스, 인터페이스에서 사용할 수 있는 유형을 제한합니다. 제네릭 제약을 사용하면 코드를 더욱 안전하고 예측 가능하게 만들 수 있습니다.

function identity<T extends number>(value: T): T {
  return value;
}

const result = identity<number>(10); // 10
const result2 = identity<string>("Hello"); // 오류: string은 숫자가 아닙니다.

위의 예시에서 identity 함수는 숫자만 받을 수 있도록 제약되어 있습니다. 따라서 문자열을 전달하면 오류가 발생합니다.

분산

분산은 제네릭 유형이 어떻게 다른 유형과 관련되는지를 나타냅니다. 공변 분산은 자식 유형을 부모 유형으로 대체할 수 있음을 의미합니다. 반면, 반변 분산은 부모 유형을 자식 유형으로 대체할 수 있음을 의미합니다.

interface Box<T> {
  item: T;
}

function box<T>(value: T): Box<T> {
  return { item: value };
}

const boxOfNumber = box<number>(1);
const boxOfString = box<string>("Hello");

// 공변 분산
const boxOfAny: Box<any> = boxOfNumber;
boxOfAny.item = "Hello"; // 정상

// 반변 분산
const array: Array<number> = [1, 2, 3];
const boxedArray: Array<Box<number>> = array.map(box);
boxedArray[0].item = "Hello"; // 오류: string은 숫자가 아닙니다.

위의 예시에서 Box 클래스는 공변 분산입니다. 따라서 Box<number>를 Box<any>로 대체할 수 있습니다. 하지만 Array 클래스는 반변 분산입니다. 따라서 Array<number>를 Array<Box<number>>로 대체할 수 없습니다.