Java같은 정적 타입 언어는 함수 및 클래스를 정의하는 시점에 매개변수나 리턴 값의 타입을 선언해줘야 하는데, 이처럼 어떠한 클래스 혹은 함수에서 사용할 타입을 그 함수나 클래스를 사용할 때 결정하는 프로그래밍 기법을 제네릭이라 한다.
TypeScript에서의 제네릭은 기법 보다는 기능에 조금 가까운 느낌인데 기본적으로 타입을 정의한 함수 혹은 클래스는 모두 다른 타입에 재사용할 수 없다. 그러나 제네릭을 사용하면 각 상황에 맞게끔 여러 가지 타입으로 함수 및 클래스를 재 사용 할 수 있다
// any[]
const printZero: any = (array: any[]) => {
return array[0];
};
만일 매개변수로 상황에 따라 특정 데이터들로 구성된 배열을 받아서 0번째 인덱스를 리턴하는 함수 printZero
가 있다고 했을때 상황에따라 'number', 'string'도 받을 수 있으므로 타입을 any[]
로 지정할 수 밖에 없을 것이고 결국 이 함수는 어떤 타입을 리턴하는지 알 수 없을 것이다. 그러나 여기에 제네릭을 사용한다면 호출에 따라 함수의 리턴 타입이 정해지기 때문에 안전하면서도 재활용 가능한 함수를 만들 수 있게된다.
// Generics
const printZero = function <T>(array: T[]): T {
return array[0];
};
printZero<number>([1, 2, 3, 4]);
// 1
// Generics (Arrow function)
const printZero = <T,>(array: T[]): T => {
return array[0];
};
printZero<number>([1, 2, 3, 4]);
// 1
위 예제 처럼 제네릭을 선언할 때 <T>
를 사용하면 된다 여기서의 'T'는 관용적으로 사용되는 식별자로 타입 파라미터라고 한다. T는 Type의 약자이며 반드시 T를 사용하여야 하는 것은 아니다.
위 예제 코드를 작성 후 printZero
함수 위에 커서를 올리면 위 스크린샷 처럼 number[]를 받고, number타입을 리턴하는 함수로 나오지만,
// Generics
const printZero = function <T>(array: T[]): T {
return array[0];
};
printZero<string>([1, 2, 3, 4]);
// 1
호출 시점에서 타입을
string
으로 지정해주면 해당 함수의 타입이 호출 시 지정해준 타입으로 변하게된다.이처럼 제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이며, 한번의 선언으로 다양한 타입에 재사용이 가능하다.
const printZero = <T, U>(a: T, b: U): [T, U] => {
return [a, b];
};
printZero<number, string>(1, '2');
// [1,'2']
만일 두개 이상의 매개변수를 받는 함수라면 T의 다음 알파벳인 U를 사용해서 위 예제 처럼 표현 해 줄 수 있다.