[TypeScript] 제네릭(Generic)
타입스크립트를 공부하다 보면 함수나 인터페이스 뒤에 붙은 <T> 같은 기호를 본 적이 있을 것이다. 이것이 바로 제네릭(Generic) 이다. 제네릭은 단일 타입이 아닌 다양한 타입에서 동작하는 컴포넌트를 작성할 수 있게 해주는 타입스크립트의 놀라운 기능이다. 쉽게 말해 타입을 변수처럼 유연하게 다루는 도구라고 볼 수 있다. 1. 제네릭이 필요한 이유 제네릭을 왜 써야 하는지 이해하기 위해, 인수로 받은 값을 그대로 반환하는 간단한 함수 func를 만들어보자. 시도 1: any 타입 사용하기 다양한 타입을 받아야 하니, 일단 매개변수 타입을 any로 설정해 보았다. TypeScript function func(value: any) { return value; } let num = func(10); let str = func("string"); 이 코드는 잘 동작하는 것처럼 보이지만 치명적인 단점이 있다. 반환값의 타입마저 any가 되어버린다는 점이다. TypeScript num.toUpperCase(); // ❌ 런타임 오류 발생! num에는 분명 숫자 10이 들어있지만, 타입스크립트는 이를 any로 인식하기 때문에 문자열 메서드인 .toUpperCase()를 써도 에러를 잡아내지 못한다. 결국 실행 시점에 프로그램이 뻗어버리는 위험한 상태가 된다. 시도 2: unknown 타입 사용하기 그렇다면 any보다 안전한 unknown을 쓰면 어떨까? TypeScript function func(value: unknown) { return value; } let num = func(10); // num의 타입은 unknown // num.toFixed(); // ❌ 오류 발생 (unknown에는 메서드 사용 불가) unknown은 안전하지만 너무 엄격하다. 값을 사용하려면 매번 typeof 등을 이용해 타입 좁히기를 해야 한다. TypeScript if (typeof num === "number") { num.toFixed(); // 귀찮은 과정이 필요함 } 우리가 원하는 건 단순하다. "숫자를 넣으면 숫자가 반환되고, 문자를 넣으면 문자가 반환되는" 그런 유연한 함수다. 이럴 때 제네릭이 필요하다. --- 2. 제네릭(Generic) 함수 제네릭(Generic) 이라는 단어는 '일반적인', '포괄적인'이라는 뜻을 가진다. 즉, 모든 타입의 값을 두루두루 포괄하여 적용할 수 있는 범용적인 함수를 만든다는 의미다. 제네릭 기본 문법 <T> 제네릭 함수는 함수 이름 뒤에 꺾쇠 < >를 열고 타입 변수를 선언하여 사용한다. 보통 T (Type의 약자)를 많이 쓴다. TypeScript // <T>: 타입 변수 선언 // (value: T): 매개변수 타입 // : T : 반환값 타입 function func<T>(value: T): T { return value; } 이제 이 함수를 호출할 때 전달하는 인수의 타입에 따라 T가 결정된다. TypeScript let num = func(10); // 1. 인수 10(number)이 전달됨 // 2. T가 number로 추론됨 // 3. 매개변수와 반환값 타입이 모두 number로 결정됨 // 결과: num은 number 타입 ✅ TypeScript let str = func("string"); // 1. 인수 "string"이 전달됨 // 2. T가 string으로 추론됨 // 결과: str은 string 타입 ✅ 이제 any처럼 타입 안정성을 잃지도 않고, unknown처럼 귀찮게 타입을 좁힐 필요도 없다. --- 3. 명시적으로 타입 지정하기 대부분의 경우 타입스크립트가 타입을 알아서 잘 추론해 주지만, 가끔은 우리가 직접 타입을 지정해야 할 때가 있다. 예를 들어 배열을 넣었을 때, 튜플 타입으로 추론되길 원한다면 다음과 같이 명시할 수 있다. TypeScript // 자동으로 추론시키면? let arr = func([1, 2, 3]); // T는 number[] 로 추론됨 // 명시적으로 지정하면? let tuple = func<[number, number, number]>([1, 2, 3]); // T는 [number, number, number] 튜플 타입으로 결정됨 함수 호출 시 <타입>을 직접 적어주면, 타입스크립트는 추론하지 않고 우리가 적어준 타입을 T에 할당한다. --- 요약 1. 문제점: any는 타입 검사를 포기하고, unknown은 사용하기 너무 번거롭다. 2. 해결책: 제네릭을 사용하면 입력값의 타입에 따라 반환값의 타입이 유연하게 결정된다. 3. 문법: 함수 이름 뒤에 <T>를 붙여 타입 변수를 선언하고 사용한다. 4. 사용: 일반적으로는 타입이 자동 추론되지만, 필요하다면 <Type>을 명시하여 호출할 수 있다.