본문 바로가기
프로그래밍 언어/Typescript

[타입스크립트] 제네릭

by 스코필 2024. 1. 17.

제네릭 정의

제네릭(Generics)이란 타입을 마치 함수의 파라미터처럼 사용하는 것을 의미한다. 

제네릭을 사용하면 클래스나 함수 인터페이스를 다양한 타입으로 재사용할 수 있다.

 

예를 들어, 다음과 같은 함수를 실행하려고 한다.

function getSize(arr): number {
  return arr.length;
}

 

위의 함수는 리스트의 길이를 반환해주는 함수이다. 만약 리스트의 요소들이 숫자형, 문자형 등 다양한 타입들로 되어 있다면 어떻게 처리해주어야 할까?

function getSize(arr: any): number {
  return arr.length;
}

getSize([1, 2, 3]);
getSize(["a", "b", "c"]);
getSize([true, false, true]);

 

첫 번째는 arr의 타입을 any로 처리하면 된다. 하지만 이 것은 어떤 타입이 들어가도 상관없기 때문에 타입스크립트의 장점을 살릴 수 없다.

function getSize(arr: number[] | string[] | boolean[]): number {
  return arr.length;
}

getSize([1, 2, 3]);
getSize(["a", "b", "c"]);
getSize([true, false, true]);

 

두 번째 방법은 옵셔널하게 주어 타입을 제한할 수 있다. 하지만 객체형 리스트 등 또 다른 타입의 리스트가 들어오면 추가적으로 타입을 작성해야 하기 때문에 매우 불편하다.

위의 문제들을 해결하기 위해서 제네릭을 사용한다.

 

제네릭 사용 방법

제네릭은 함수나 인터페이스 뒤에 < > 을 붙이고, 사이에 T(일반적)를 사용한다.

여기서 T는 제네릭을 선언할 때 관용적으로 사용되는 식별자로 타입 파라미터라 한다.

제네릭을 사용하여 위의 함수를 다시 구현하면, 아래와 같이 할 수 있다.

function getSize<T>(arr : T[]): number {
  return arr.length;
}

getSize<number>([1, 2, 3])
getSize<string>(["a", "b", "c"]);
getSize<boolean>([true, false, true]);

 

다른 타입의 리스트가 들어온다면, 다음과 같이 구현할 수 있다.

function getSize<T>(arr : T[]): number {
  return arr.length;
}

getSize<number>([1, 2, 3])
getSize<string>(["a", "b", "c"]);
getSize<boolean>([true, false, true]);
getSize([{name : "Tom"}, {}, {}]); # 타입을 지정 해주지 않아도 타입스크립트는 전달되는 매개변수의 타입을 알기 때문에 생략 가능.

 

인터페이스 제네릭

제네릭은 인터페이스에 적용할 수도 있다.

interface Car<T> {
  name: string;
  price: number;
  option: T;      // 여러 가지 타입이 올 수 있다는 것을 지정
}

const c1: Car<{ color: string; year: number }> = {
  name: "c10",
  price: 1000,
  option: {
    color: "red",
    year: 2000,
  },
};

const c2: Car<string> = {
  name: "c11",
  price: 500,
  option: "nice",
};

 

제네릭 제약 조건 (extends)

제네릭에 extends 키워드를 이용하면 제네릭 타입으로 입력할 수 있는 타입의 종류를 제한할 수 있다.

<T extends K> 형태의 제네릭이 있다면, T가 K에 할당 가능해야 한다 라고 정의한다.

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

interface Car {
  name: string;
  color: string;
}

interface Book {
  name: number;
  price: number;
}

const user: User = { name: "a", age: 10 };
const car: Car = { name: "bmw", color: "red" };
const book: Book = { name: 1, price: 3000 };

function showName<T extends { name: string }>(data: T): string {
  return data.name;
}

showName(user);
showName(car);
showName(book); // name의 타입이 string이 아니므로 error를 반환