Generic
제네릭
타입을 파라미터화하는 기법. 코드 재사용성과 타입 안전성 향상.
제네릭
타입을 파라미터화하는 기법. 코드 재사용성과 타입 안전성 향상.
제네릭(Generic)은 타입을 매개변수처럼 전달받아 다양한 타입에서 동작하는 코드를 작성하는 프로그래밍 기법입니다. 컴파일 시점에 타입이 결정되므로 런타임 에러 없이 타입 안전성을 보장하면서도 코드 중복을 크게 줄일 수 있습니다.
제네릭이 없던 시절에는 모든 타입을 처리하려면 Object나 any를 사용하거나, 각 타입마다 동일한 로직의 함수를 별도로 작성해야 했습니다. 제네릭을 사용하면 하나의 함수나 클래스로 숫자, 문자열, 객체 등 어떤 타입이든 안전하게 처리할 수 있어 DRY(Don't Repeat Yourself) 원칙을 지킬 수 있습니다.
제네릭은 TypeScript, Java, C#, Go, Rust 등 대부분의 현대 정적 타입 언어에서 지원합니다. 특히 컬렉션(배열, 리스트, 맵 등), API 응답 처리, 유틸리티 함수 등에서 필수적으로 사용되며, 라이브러리나 프레임워크를 개발할 때 핵심적인 역할을 합니다.
실무에서 제네릭은 API 응답 타입 정의, 상태 관리 라이브러리의 스토어 타입, React 컴포넌트의 Props 타입 등 다양한 곳에서 활용됩니다. 특히 TypeScript 프로젝트에서는 제네릭을 잘 활용하면 IDE의 자동완성과 타입 추론 기능을 극대화하여 개발 생산성을 크게 높일 수 있습니다.
// 1. 기본 제네릭 함수 - 타입을 매개변수로 받음
function identity<T>(value: T): T {
return value;
}
// 사용 시 타입이 자동 추론됨
const num = identity(42); // T = number
const str = identity("hello"); // T = string
// 2. 제네릭 인터페이스 - API 응답 타입 정의
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: Date;
}
interface User {
id: number;
name: string;
email: string;
}
// 사용자 API 응답
const userResponse: ApiResponse<User> = {
data: { id: 1, name: "김철수", email: "kim@example.com" },
status: 200,
message: "성공",
timestamp: new Date()
};
// 3. 제네릭 제약조건 (extends) - 특정 타입만 허용
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): void {
console.log(`길이: ${item.length}`);
}
logLength("hello"); // 문자열은 length 있음 ✅
logLength([1, 2, 3]); // 배열도 length 있음 ✅
// logLength(123); // 숫자는 length 없음 ❌ 컴파일 에러
// 4. 제네릭 유틸리티 함수 - 배열에서 첫 번째 요소 추출
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const firstNum = first([1, 2, 3]); // number | undefined
const firstUser = first([userResponse.data]); // User | undefined
// 5. 제네릭 클래스 - 타입 안전한 스택 구현
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
const numberStack = new Stack<number>();
numberStack.push(10);
numberStack.push(20);
console.log(numberStack.pop()); // 20 (number 타입)
"API 응답 타입을 매번 새로 만들지 말고, ApiResponse<T> 제네릭으로 공통화합시다. data 부분만 제네릭 타입으로 받으면 모든 엔드포인트에서 재사용할 수 있어요."
"제네릭은 컴파일 타임에 타입 안전성을 보장하면서 코드 재사용성을 높이는 기법입니다. any를 쓰면 런타임 에러 위험이 있지만, 제네릭은 타입 추론과 자동완성을 지원해서 개발 생산성도 높아집니다."
"여기 Object로 받고 있는데, 제네릭으로 바꾸면 어떨까요? extends 제약조건으로 필요한 프로퍼티만 강제하면 타입 캐스팅 없이도 안전하게 사용할 수 있습니다."
any를 쓰면 타입 검사를 포기하게 됩니다. "일단 any로 해두자"라고 생각하면 나중에 런타임 에러로 돌아옵니다. 타입을 모르면 unknown이나 제네릭을 사용하세요.
T, U, V 같은 단일 문자 대신 의미 있는 이름을 쓰세요. TData, TResponse, TItem처럼 T 접두사와 함께 의미를 담으면 코드 가독성이 높아집니다.
제네릭이 모든 타입을 받을 필요는 없습니다. extends를 사용해 필요한 속성이나 메서드를 가진 타입만 받도록 제한하면 더 안전한 코드를 작성할 수 있습니다.