제네릭은 C#이나 Java와 같은 언어에서 제공하는 강력한 도구로, 다양한 타입에 대해 재사용 가능한 컴포넌트를 생성할 수 있게 해줍니다. 타입스크립트에서도 제네릭을 활용하면 여러 타입에서 동작할 수 있는 유연하면서도 안전한 코드를 작성할 수 있습니다.
제네릭은 타입을 함수의 파라미터처럼 사용하는 것을 의미합니다.
제네릭은 함수, 클랫, 인터페이스 또는 타입에 적용될 수 있으며, 사용 시점에 구체적인 타입을 전달합니다.
identity 함수는 인수로 전달된 값을 그대로 반환하는 단순한 함수입니다. 제네릭이 없을 때는 아래와 같이 작성할 수 있습니다.
// 특정 타입으로 제한된 경우
function identity(arg: number): number {
return arg;
}
// any 타입 사용
function identity(arg: any): any {
return arg;
}
any
타입을 사용한 경우는 유연성을 제공하지만, 함수의 반환 타입을 정확히 알 수 없어 타입 안정성을 잃게 됩니다.function identity<T>(arg: T): T {
return arg;
}
여기서 T
는 타입 변수로, 사용자가 제공한 타입을 캡처합니다. 이후 함수에서 이 정보를 활용해 반환값의 타입을 유지할 수 있습니다.
보통 T는 Type의 약어로 사용되지만, 필요에 따라 U, K, V 등 다른 알파벳을 활용할 수 있습니다.
T: Type
K: Key
V: Value
U: User-defined Type or Another Type
// 명시적으로 타입 전달
let a = identity<string>("Hello, TypeScript");
// 타입스크립트가 타입 추론
let b = identity(100);
컴파일 시점에 타입을 강제할 수 있어 런타임 에러를 줄입니다.
다양한 타입에 대해 동작하는 함수나 클래스를 작성할 수 있습니다.
any
와 달리 반환 타입을 명확히 알 수 있어 코드의 이해도를 높입니다.
기본적으로 제네릭은 아무 타입이나 받을 수 있지만, 특정 조건을 부여할 수도 있습니다.
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
getLength("Hello"); // 정상
getLength([1, 2, 3]); // 정상
getLength(123); // 오류: 'number'에는 'length' 속성이 없음
function combine<T extends string | number>(a: T, b: T): T {
return (a as any) + (b as any); // 타입스크립트에서는 명시적 단언이 필요
}
combine(1, 2); // 정상
combine("a", "b"); // 정상
combine(true, false); // 오류
제네릭은 클래스에서도 사용이 가능합니다.
class GenericBox<T> {
private _value: T;
constructor(value: T) {
this._value = value;
}
getValue(): T {
return this._value;
}
setValue(value: T): void {
this._value = value;
}
}
let stringBox = new GenericBox<string>("Hello Generics");
console.log(stringBox.getValue()); // "Hello Generics"
let numberBox = new GenericBox<number>(100);
console.log(numberBox.getValue()); // 100
제네익은 인터페이스에서 또한 사용이 가능합니다.
interface Pair<K, V> {
key: K;
value: V;
}
let pair: Pair<string, number> = { key: "age", value: 27 }
✅ 참고
제네릭 너 F야?