타입을 마치 함수의 파라미터처럼 사용하는 것을 의미한다. 하나의 타입이 아닌 여러 타입에 대해 클래스, 인터페이스, 함수가 동일하게 동작할 수 있도록 하는 방법이다.

함수에서 무엇이 반환되는지를 표시할 수 있도록 인수의 타입을 캡쳐하는 방법으로 제네릭을 사용한다.

function getText<**T**>(text: T): T { // T를 마치 파라미터처럼 사용할 수 있다.
  return text
}

getText('hi') // 'hi' -> T: string
getText(10)   // 10 -> T: number
getText(true) // true -> T: boolean

getText<string>('hi') // 'hi' -> T: string
getText<number>(10)   // 10 -> T: number
getText<boolean>(true) // true -> T: boolean

이렇게 제네릭을 사용하게 되면, text에 어떤 값이 들어오던지 그 타입을 그대로 반환하는 함수를 작성할 수 있다.

제네릭을 사용하지 않는다면 오직 any만을 사용하게 되는데, 이게 그리 좋은 코드는 아니라, 함수의 인자로 어떤 타입이 들어갔고, 어떤 값이 반환되는지 명확하게 표기하기 위해서는 제네릭을 사용하는 것이 좋다.

제네릭에서의 타입 정하기

만약 배열 인자를 받아서 이 안의 배열 메서드를 사용한다고 해 보자.

function loggingIdentity<T>(arg: T): T {
  console.log(arg.length); // 오류: T에는 .length 가 없습니다.
  return arg;
}

이렇게 오류가 난다. 왜냐면 T에는 배열이 아니라 다른 타입의 인자들도 들어올 수 있기 때문이다. 따라서 이렇게 임의의 타입에 설정되어 있는 메서드를 사용하기 위해서는 타입을 확정시킬 필요가 있다.

function loggingIdentity<T>(arg: T[]): T[] {
  console.log(arg.length); // 배열은 .length를 가지고 있습니다. 따라서 오류는 없습니다.
  return arg;
}

or

function loggingIdentity<T>(arg: Array<T>): Array<T> {
  console.log(arg.length); // 배열은 .length를 가지고 있습니다. 따라서 오류는 없습니다.
  return arg;
}