제네릭이란 함수나 인터페이스, 타입 별칭, 클래스 등을 다양한 타입과 함께 동작하도록 만들어주는 타입스크립트의 기능 중 하나이다.
다음과 같이 호출 결과를 저장하는 num, str
등의 변수가 any
타입으로 추론되면 문제가 발생한다.
function func(value: any) {
return value;
}
let num = func(10);
let str = func("string");
num.toUpperCase()
num
에는 분명 Number
타입의 값 10
이 저장되어 있을 것이다. 그러나 any
타입으로 추론되어 버렸기 때문에 toUpperCase
등의 String
타입의 메서드를 사용해도 타입스크립트가 오류를 감지하지 못한다. 이 코드는 결국 실제로 실행하면 런타임 오류를 발생시키는 아주 위험한 상태가 되는 것이다.
이번에는 매개변수의 타입을 unknown
타입으로 정의해보자.
function func(value: unknown) {
return value;
}
let num = func(10);
// unknown 타입
let str = func("string");
// unknown 타입
num.toUpperCase(); // ❌
num.toFixed(); // ❌
toUpperCase
같은 메서드 호출은 방지할 수 있으나 toFixed
같은 Number
타입의 메서드 호출도 함께 오류로 판단하게 된다.
따라서, num
에 10이 저장될 것이 분명한데도 이 값을 사용하려면 다음과 같이 비 효율적으로 타입 좁히기를 이용해야 한다.
function func(value: unknown) {
return value;
}
let num = func(10);
// unknown 타입
let str = func("string");
// unknown 타입
if (typeof num === "number") {
num.toFixed();
}
이런 상황에서 제네릭을 이용하면 문제는 간단히 해결된다.
제네릭 함수는 두루두루 모든 타입의 값을 다 적용할 수 있는 그런 범용적인 함수라고 이해할 수 있다.
function func<T>(value: T): T {
return value;
}
제네릭 함수를 호출할 때 다음과 같이 타입 변수에 할당할 타입을 직접 명시하는 것도 가능하다.
function func<T>(value: T): T {
return value;
}
let arr = func<[number, number, number]>([1, 2, 3]);
위 코드의 흐름은 다음과 같다.
1. T
에 [Number, Number, Number]
튜플 타입이 할당됨
1. 매개변수 value
와 반환값 타입이 모두 튜플 타입이 됨
만약 위 코드에서 타입 변수에 할당할 타입을 튜플 타입으로 설정하지 않았다면 T
가 number[]
타입으로 추론 되었을 것이다. 타입스크립트는 타입을 추론할 때 항상 일반적이고 좀 더 범용적인 타입으로 추론하기 때문이다.
이렇듯 타입 변수에 할당하고 싶은 특정 타입이 존재한다면 함수 호출과 함께 꺽쇠를 열고 직접 명시해주는게 좋다. 그렇지 않은 대다수의 상황에서는 알아서 잘 추론되기 때문에 굳이 타입 변수를 설정하지 않아도 된다.