본문 바로가기
카테고리 없음

TypeScript 제네릭(Generic)의 모든 것 – 재사용 가능한 타입 만들기 (16강)

by mystory55776 2025. 5. 16.

TypeScript 제네릭(Generic)의 모든 것 – 재사용 가능한 타입 만들기 (16강)

**TypeScript 제네릭(Generic)**은 타입을 **동적으로** 정의할 수 있는 매우 유용한 기능으로, 코드의 재사용성과 타입 안전성을 동시에 높일 수 있습니다. 제네릭을 활용하면 **타입을 매개변수화**하여 다양한 데이터 타입을 처리할 수 있는 함수나 클래스를 만들 수 있습니다. 이번 강의에서는 **TypeScript 제네릭**의 개념과 사용법을 자세히 설명하고, 제네릭을 활용하여 **재사용 가능한 타입**을 어떻게 만들 수 있는지 알아보겠습니다.

제네릭(Generic) 개념 이해하기

**제네릭(Generic)**은 **타입 매개변수**를 사용하여 타입을 **유연하게 다룰 수 있는** 기능입니다. 제네릭을 사용하면 특정 데이터 타입에 구애받지 않고 **함수**나 **클래스**를 정의할 수 있어, 다양한 타입에 대해 **재사용 가능한 코드**를 작성할 수 있습니다.

예를 들어, 함수의 매개변수와 반환값이 어떤 타입인지 명확히 알 수 없을 때, 제네릭을 사용하면 이를 **동적으로 정의**할 수 있습니다. 이렇게 함으로써 코드의 **유연성**과 **타입 안정성**을 높일 수 있습니다.

TypeScript 제네릭 기본 문법

TypeScript에서 제네릭을 사용하는 기본 문법은 다음과 같습니다. 제네릭은 ****와 같은 형태로 사용되며, 이 **T**는 타입 매개변수를 의미합니다. 함수나 클래스가 호출될 때 타입 매개변수에 적합한 타입이 할당됩니다.

1. 제네릭 함수 예제

가장 기본적인 제네릭 함수의 예제를 살펴보겠습니다. 제네릭 함수는 다양한 데이터 타입을 처리할 수 있도록 **타입을 매개변수화**하여 정의할 수 있습니다.

function identity(arg: T): T {
  return arg;
}

let result1 = identity(42);  // number 타입
let result2 = identity("Hello, world!");  // string 타입

위 예제에서 **identity** 함수는 제네릭 함수로, **T**는 함수의 **입력 타입**과 **출력 타입**을 모두 매개변수화합니다. `identity(42)`를 호출하면 **number** 타입이 **T**에 할당되고, `identity("Hello, world!")`를 호출하면 **string** 타입이 **T**에 할당됩니다.

2. 제네릭 배열 함수

제네릭을 사용하여 배열의 타입도 동적으로 정의할 수 있습니다. 예를 들어, 배열의 각 요소가 모두 같은 타입임을 보장하는 함수는 다음과 같이 작성할 수 있습니다.

function printArray(arr: T[]): void {
  arr.forEach((item) => console.log(item));
}

printArray([1, 2, 3]);  // number 배열
printArray(["apple", "banana", "cherry"]);  // string 배열

**printArray** 함수는 제네릭을 사용하여 배열의 타입을 **동적으로 지정**합니다. 이 함수는 **number 배열**과 **string 배열**을 모두 처리할 수 있습니다. 제네릭을 통해 배열의 요소 타입을 확정할 수 있습니다.

제네릭 인터페이스와 클래스

제네릭을 함수 외에도 **인터페이스**와 **클래스**에서 사용할 수 있습니다. 제네릭을 클래스나 인터페이스에 적용하면, 다양한 타입을 처리하는 **재사용 가능한** 클래스를 작성할 수 있습니다.

1. 제네릭 인터페이스

제네릭 인터페이스를 사용하면 **다양한 타입의 객체**를 처리하는 인터페이스를 정의할 수 있습니다. 예를 들어, **데이터베이스**에서 정보를 다루는 인터페이스를 제네릭으로 작성할 수 있습니다.

interface Box {
  value: T;
}

let numberBox: Box = { value: 42 };
let stringBox: Box = { value: "Hello" };

위 예제에서 **Box** 인터페이스는 **value** 속성의 타입을 **T**로 지정하여, 다양한 타입의 값을 **box** 객체로 감쌀 수 있습니다. 이를 통해 같은 인터페이스를 사용하면서도 다른 타입을 처리할 수 있습니다.

2. 제네릭 클래스

**제네릭 클래스**는 객체의 속성이나 메서드가 다양한 타입을 처리할 수 있도록 합니다. 제네릭을 사용하여 **재사용 가능한 클래스**를 쉽게 만들 수 있습니다.

class Container {
  private items: T[] = [];

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

  getItems(): T[] {
    return this.items;
  }
}

let numberContainer = new Container();
numberContainer.add(1);
numberContainer.add(2);
console.log(numberContainer.getItems());  // [1, 2]

let stringContainer = new Container();
stringContainer.add("apple");
stringContainer.add("banana");
console.log(stringContainer.getItems());  // ["apple", "banana"]

위 예제에서 **Container** 클래스는 제네릭을 사용하여 다양한 타입의 **배열**을 저장할 수 있는 클래스를 정의합니다. `numberContainer`와 `stringContainer`를 생성하면서, 각각 **number** 타입과 **string** 타입의 데이터를 처리할 수 있습니다.

제네릭을 활용한 고급 기능

제네릭을 활용하여 좀 더 고급 기능을 구현할 수도 있습니다. 예를 들어, 제네릭을 **제약 조건**(constraints)을 설정하여 특정 타입만 허용하도록 만들 수 있습니다.

1. 제네릭 제약 조건

**제네릭 제약 조건**을 사용하면 특정 **타입**만 제네릭으로 사용할 수 있도록 제한할 수 있습니다. 예를 들어, 객체가 특정 속성을 갖는 경우에만 제네릭 타입을 사용할 수 있습니다.

interface Lengthwise {
  length: number;
}

function logLength(item: T): void {
  console.log(item.length);
}

logLength("Hello, world!");  // 13
logLength([1, 2, 3, 4]);  // 4

위 예제에서 **T extends Lengthwise**는 **T**가 **length** 속성을 가진 객체에만 적용되도록 제약을 걸어줍니다. 이를 통해 **배열**이나 **문자열**처럼 **length** 속성을 가진 타입만 **logLength** 함수에서 사용될 수 있습니다.

2. 여러 제네릭 매개변수

**여러 제네릭 매개변수**를 사용하여 함수나 클래스에서 복잡한 타입을 처리할 수 있습니다. 예를 들어, 두 가지 이상의 타입을 다루는 함수나 클래스를 만들 수 있습니다.

function pair(first: T, second: U): [T, U] {
  return [first, second];
}

let result = pair("hello", 42);  // [string, number]
console.log(result);

**pair** 함수는 **T**와 **U**라는 두 가지 타입을 받아 **튜플**로 반환합니다. 이를 통해 다양한 **복합적인 타입**을 처리할 수 있습니다.

제네릭을 활용한 TypeScript 코드의 장점

**제네릭(Generic)**을 활용하면 코드의 **재사용성**과 **타입 안정성**을 높일 수 있습니다. 제네릭을 통해 타입을 동적으로 다루면서도 컴파일 시점에 타입 검사를 강화할 수 있기 때문에, 버그를 사전에 예방할 수 있습니다.

  • 타입 안전성: 제네릭은 코드에서 잘못된 타입 사용을 방지하여 타입 안정성을 높입니다.
  • 유연성: 제네릭을 사용하면 다양한 타입을 처리할 수 있어 코드의 유연성이 향상됩니다.
  • 재사용성: 제네릭을 활용한 함수나 클래스는 다양한 데이터 타입에 대해 재사용이 가능하여 코드의 유지보수성이 높아집니다.

결론

**TypeScript 제네릭(Generic)**은 **타입을 매개변수화**하여 다양한 데이터 타입을 안전하고 유연하게 처리할 수 있는 기능입니다. 제네릭을 활용하면 **재사용 가능한 코드**를 작성할 수 있으며, 코드의 **유연성**과 **타입 안정성**을 모두 보장할 수 있습니다. 이번 강의를 통해 제네릭의 기본 개념과 고급 기능을 이해하고, 실제 코드에 적용해 보시기 바랍니다.