g*g 2024. 2. 25. 16:05

제네릭은 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 리액트