제네릭(generic)은 하나의 데이터 타입이 아닌 여러 데이터 타입에 대해 클래스/인터페이스 혹은 함수가 동일하게 동작할 수 있게 해주는 기능이다. 쉽게 말해, 타입을 변수화 한 것이다.
제네릭에는 함수에 제네릭 타입을 적용한 제네릭 함수, 인터페이스에 제네릭 타입을 넣은 제네릭 인터페이스, 클래스에 제네릭을 적용한 제네릭 클래스 등이 있다.
제네릭을 사용하면 재사용성이 높은 함수와 클래스를 생성할 수 있다. 그리고 오류를 쉽게 포착할 수 있다.
// number 타입의 매개변수를 return하는 함수
function NumberReturnFunc(arg: number): number {
return arg;
}
// string 타입의 매개변수를 return하는 함수
function StringReturnFunc(arg: string): string {
return arg;
}
// boolean 타입의 매개변수를 return하는 함수
function BooleanReturnFunc(arg: boolean): boolean {
return arg;
}
위는 매개변수를 그대로 반환하는 함수들인데 함수의 기능은 똑같지만, 매개변수의 타입과 반환하는 타입이 다르다는 이유로 여러 개의 함수를 구현했다. 이럴 때 제네릭 함수를 사용하여 한 개의 함수로 구현할 수 있다.
function GenericReturnFunc<T>(arg: T): T {
return arg;
}
제네릭 함수 구현 방법은 함수명 뒤에 <T>를 추가하며, T를 매개변수의 타입 또는 반환 타입으로 설정할 수 있다. 꼭 <T>로 작성할 필요는 없다.
위에서 만든 제네릭 함수는 다음과 같이 호출할 수 있다.
let numVar = GenericReturnFunc<number>(123);
let strVar = GenericReturnFunc<string>('ABC');
let GenericReturnFunc = <Type>(arg: Type): Type => {
return arg;
}
제네릭 화살표 함수를 위와 같이 구현하는 경우 에러가 발생한다. .ts 확장자 파일에서는 정상적으로 작동할 수 있는데 .tsx 확장자 파일은 TypeScript + JSX로 구성되어 있어서 <Type>에서 태그(<>) 문제가 발생한다.
.tsx 확장자 파일에서 제네릭 화살표 함수를 구현해야 하는 경우 제네릭 매개변수에 extends를 사용하여 컴파일러에게 제네릭 화살표 함수라고 알려주어야 한다.
let GenericReturnFunc = <Type extends {}>(arg: Type): Type => {
return arg;
}
// 제네릭 인터페이스
interface Mobile<T> {
name: string;
price: number;
option: T; // 제네릭 타입 - option 속성에는 다양한 타입의 데이터가 들어온다
}
// 제네릭 자체에 리터럴 객체 타입 할당
const m1: Mobile<{ color: string; coupon: boolean }> = {
name: 's21',
price: 1000,
option: { color: 'black', coupon: false }, // 제네릭 타입에 의해 option 속성 할당
};
const m2: Mobile<string> = {
name: 's20',
price: 900,
option: 'used' // 제네릭 타입에 의해 option 속성 할당
};
class Queue<T> {
protected data: Array<T> = [];
push(item: T) {
this.data.push(item);
}
pop(): T | undefined {
return this.data.shift();
}
}
const numberQueue = new Queue<number>();
numberQueue.push(0);
numberQueue.push(1);
numberQueue.push('1'); // error
제네릭의 타입 파라미터는 기본적으로 모든 타입을 받아들일 수 있다. 만약 모든 타입을 받아들이지 말고, 제약된 타입들만 받아들이고자 한다면, 이러한 제약을 타입 파라미터에 지정할 수 있는데, 이를 제네릭 타입 제약이라고 한다.
const printMessage = <T extends string | number>(message: T): T => {
return message;
}
printMessage<string>('abc'); // abc
printMessage<number>(123); // 123
printMessage<boolean>(false); // error : Type 'boolean' does not satisfy the constraint 'string | number'.
extends 키워드로 제약조건을 걸어준다. 만약 제약조건을 벗어나는 타입을 선언하면 에러가 발생한다.