제네릭(Generic)
제네릭은 C나 자바 같은 정적 언어에서 다양한 타입 간에 재사용성을 높이기 위해 사용하는 문법이다. 타입스크립트도 정적 타입을 가지는 언어이기 때문에 제네릭 문법을 지원하고 있다.
타입스크립트의 제네릭은 일반화된 데이타 타입이라고 할 수있다. 개념은 함수, 타입, 클래스 등 내부에서 사용할 타입을 미리 정의해두지 않고 타입 변수를 사용해서 해당 위치를 비워 둔 다음에, 실제로 그 값을 사용할 때 외부에서 타입 변수자리에 타입을 지정하여 사용하는 방식을 말한다.
이렇게 하면 여러 타입에 대해 하나하나 따로 정의하지 않아도 되기 때문에 재사용성이 크게 향상된다. 타입 변수는 일반적으로 <T>와 같이 꺽쇠괄호 내부에 정의되며, 사용할 떄는 함수에 매개변수를 넣는 것과 유사하게 원하는 타입을 넣어주면 된다. 보통 타입 변수명으로 T(Type), E{Element}, K(Key), V(Value) 등 한 글자로 된 이름을 많이 사용한다.
type ExampleArrayType<T> = T[];
const array1: ExampleArrayType<string> = ["치킨", "피자", "우동"];
제네릭은 any처럼 아무 타입이나 무분별하게 받는게 아니라 배열 생성 시점에 원하는 타입으로 특정할 수 있다. 배열 요소 전부 동일한 타입으로 보장할 수 있다.
type ExampleArrayType2 = any[];
const array2: ExampleArrayType2 = [
"치킨",
{
id: 0,
name: "치킨",
price: 20000,
quantity: 1,
},
99,
true,
];
제네릭함수 호출 시 반드시 꺽쇠괄호( <> )안에 타입을 명시해야하는 것은 아니다. 타입을 명시하는 부분을 생략하면 컴파일러가 인수를 보고 타입을 추론해준다. 타입 추론이 가능하 경으에는 타입명시를 생략할 수 있다.
function exampleFunc<T>(arg: T): T[] {
return new Array(3).fill(arg);
}
exampleFunc("hello"); // T는 string으로 추론된다
특정요소 타입을 알수 없을 때 제네릭 타입에 기본값을 추가할 수 있다.
interface SubmitEvent<T = HTMLElement> extends SyntheticEvent<T> {
submitter: T;
}
제네릭은 일반화된 데이터 타입을 의미한다. 함수나 클래스 등의 내부에서 제네릭을 사용할 때 어떤 타입이든 될 수 있다. 특정한 타입에서만 존재하는 멤버를 참조하려고 하면 안된다. 예를 들어 배열에만 존재하는 length 속성을 제네릭에서 참조하려고 하면 에러가 발생한다. 컴파일러는 어떤 타입이 제네릭에 전달될지 알 수 없기 때문에 모든 타입이 length 속성을 사용할 수는 없다고 알려주는 것이다.
function exampleFunc2<T>(arg: T): number {
return arg.length; // 에러 발생: Property ‘length’ does not exist on type ‘T’
}
이럴 때 제네릭 꺽쇠괄호( <> )내부에 'length 속성을 가진 타입만 받는다'는 제약을 걸어줌으로써 length 속성을 사용할 수 있게 만들수 있다.
interface TypeWithLength {
length: number;
}
function exampleFunc2<T extends TypeWithLength>(arg: T): number {
return arg.length;
}
제네릭 사용시 주의점
파일 확장자가 tsx일때 화살표 함수에 제네릭 사용 시 에러가 발생한다. tsx는 타입스크립트+ JSX이므로 제네릭의 꺽쇠괄호와 태그의 꺽쇠괄호를 혼동하여 문제가 생기는 것이다.
이러한 상황을 피하기 위해서는 제네릭 부분에 extends 키워드를 사용하여 컴파일러에게 특정 타입의 하위타입만 올 수 있음을 확실히 알려주면 된다. 보통 제네릭을 사용할 때 function키워드로 사용하는 경우가 많다.
// 에러 발생: JSX element ‘T’ has no corresponding closing tag
const arrowExampleFunc = <T>(arg: T): T[] => {
return new Array(3).fill(arg);
};
// 에러 발생 X
const arrowExampleFunc2 = <T extends {}>(arg: T): T[] => {
return new Array(3).fill(arg);
};
출처 - 우아한 타입스크립트 with 리액트