제네릭(Generic)은 TypeScript에서 가장 강력한 문법 중 하나입니다. 특히 “함수”와 “클래스”에서 제네릭을 제대로 활용하면, 데이터 타입에 의존하지 않는 재사용 가능한 로직을 만들 수 있습니다.
제네릭 함수 — 타입을 입력받는 함수
제네릭 함수는 “입력값의 타입과 반환값의 타입이 연결된 함수”를 만들 때 사용합니다. 즉, 타입 정보가 흐르는 함수라고 할 수 있습니다.
function identity<T>(value: T): T {
return value;
}
identity(10); // T = number
identity("hello"); // T = string
이 함수는 어떠한 타입이 와도 동일한 타입을 유지한 채 반환합니다. 중복 선언 없이 모든 타입을 처리할 수 있는 것이 강점입니다.
제네릭 함수 활용 예제
1. 배열에서 첫 번째 요소 반환하기
function first<T>(arr: T[]): T {
return arr[0];
}
first([1, 2, 3]); // number
first(["a", "b"]); // string
입력된 배열 타입에 따라 반환 타입도 자동으로 결정됩니다.
2. 2가지 타입을 동시에 받는 함수
function mapValue<T, U>(value: T, fn: (x: T) => U): U {
return fn(value);
}
mapValue(5, x => x.toString()); // string
두 개 이상의 타입을 처리해야 하는 상황에서 매우 유용합니다.
제네릭 제약(extends) 적용하기
제네릭을 아무 타입이나 허용하면 위험할 때가 있습니다. 이럴 때 제약 조건을 걸어 제한할 수 있습니다.
function getLength<T extends { length: number }>(value: T) {
return value.length;
}
getLength("Hello"); // OK
getLength([1,2,3]); // OK
getLength(10); // 오류
실제로 length 속성이 없는 타입을 걸러낼 수 있기 때문에 런타임 오류 예방에 큰 효과가 있습니다.
제네릭 클래스 — 타입을 파라미터로 받는 클래스
클래스에 제네릭을 사용하면 다양한 타입을 저장하거나 처리하는 자료구조, 서비스 클래스를 손쉽게 구현할 수 있습니다.
class Box<T> {
constructor(private value: T) {}
getValue(): T {
return this.value;
}
}
const numBox = new Box(123); // T = number
const strBox = new Box("hello"); // T = string
값을 어떤 타입으로 저장하든, 클래스는 타입을 기억하고 유지합니다.
제네릭 클래스 활용 예제
1. Repository 패턴 구현
API 또는 DB 데이터를 관리하는 Repository를 제네릭으로 설계하면 모든 도메인에 재사용할 수 있습니다.
class Repository<T> {
private items: T[] = [];
add(item: T) {
this.items.push(item);
}
findAll(): T[] {
return this.items;
}
}
type User = { id: number; name: string };
const userRepo = new Repository<User>();
userRepo.add({ id: 1, name: "Alice" });
userRepo.findAll(); // User[]
이 방식은 Repository, Service, Cache 클래스 등 실무에서 매우 많이 쓰입니다.
2. Queue, Stack 같은 자료구조 만들기
class Queue<T> {
private data: T[] = [];
enqueue(item: T) {
this.data.push(item);
}
dequeue(): T | undefined {
return this.data.shift();
}
}
const q = new Queue<number>();
q.enqueue(1);
q.enqueue(2);
자료구조를 타입 안정성 있게 구성할 수 있습니다.
제네릭 + keyof 조합
제네릭과 keyof를 결합하면 “객체의 키를 안전하게 처리하는 함수”를 만들 수 있습니다.
function getValue<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const person = { name: "Bob", age: 30 };
getValue(person, "name"); // OK
getValue(person, "email"); // 오류
이 패턴은 폼 검증, API 데이터 파싱, Config 관리 등 실무에서 매우 중요합니다.
제네릭을 활용한 API 응답 타입 최적화
API 구조가 비슷할 때 제네릭은 중복을 극적으로 줄여줍니다.
type ApiResponse<T> = {
success: boolean;
data: T;
};
const userRes: ApiResponse<{ id: number }> = {
success: true,
data: { id: 1 }
};
API 응답 타입을 매번 새로 만들지 않아도 되므로 유지보수성이 크게 올라갑니다.
제네릭 활용 시 주의해야 할 점
- 기능이 필요 없으면 제네릭을 사용하지 말 것 (과한 제네릭 남용 금지)
- 제약 조건을 적절히 걸어 타입 안전성 유지
- 타입 단언(as) 남발 대신 제네릭 타입 흐름 설계
- any를 넣으면 제네릭 의미가 사라짐
제네릭은 강력하지만 목적에 맞게 설계하지 않으면 오히려 복잡도만 증가할 수 있습니다.
제네릭 함수와 클래스는 타입 안정성, 재사용성, 도메인 확장을 모두 만족시키는 기능입니다. 특히 대규모 프로젝트에서 중복된 함수나 서비스를 만들지 않도록 도와주며, 타입 흐름을 일관되게 유지할 수 있도록 돕습니다.
- 제네릭 함수 → 타입 흐름을 보존하는 유연한 함수
- 제네릭 클래스 → 자료구조·서비스 클래스에 최적
- extends 제약 → 안전한 타입 제한
- keyof + 제네릭 → 안전한 객체 접근 패턴
'개발 > Typescript' 카테고리의 다른 글
| [TYSCRIPT] 제네릭 제약조건(extends) 사용법 — 타입 안정성을 높이는 핵심 기법 (0) | 2025.12.10 |
|---|---|
| [TYPESCRIPT] 제네릭(Generic) 기초 이해하기 - 타입을 입력받는 유연한 타입 설계 (0) | 2025.12.08 |
| [TYPESCRIPT] 컴파일러 옵션 정리 — strict, noImplicitAny 등 꼭 알아야 할 핵심 옵션 (0) | 2025.12.07 |
| [TYPESCRIPT] 타입 확장과 중복 방지 패턴 - 유지보수성을 극대화하는 TypeScript 실무 전략 (0) | 2025.12.06 |
| [TYPESCRIPT] keyof와 typeof 활용하기 - 객체 기반 타입 설계를 정교하게 만드는 핵심 문법 (0) | 2025.12.05 |
