제네릭은 코드의 재사용성과 타입 안정성을 동시에 확보할 수 있도록 도와주는 기능이다. 즉, 구체적인 타입을 미리 정하지 않고, 필요할 때마다 유연하게 타입을 지정할 수 있게 해준다.
함수를 작성할 때 특정 타입에 의존하지 않고, 여러 타입에 대해 동작하도록 만들고 싶을 때 제네릭을 사용한다.
예를 들어, 입력받은 값을 그대로 반환하는 함수
function identity<T>(arg: T): T {
return arg;
}
const result1 = identity<string>("Hello"); // string type
const result2 = identity<number>(123); // number type
여기서 <T>
는 타입 변수로, 함수를 호출할 때 실제 타입으로 대체된다. 이를 통해 함수가 어떤 타입을 받아도 그에 맞는 타입을 반환할 수 있게 된다.
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
const userReesponse: ApiResponse<{ name: string; age: number }> = {
data: { name: "joo", age: 29},
status: 200,
message: "성공"
};
여기서 T는 사용 시점에 구체적인 타입(객체)을 지정하게 된다.
클래스에서 제네릭을 사용하면 여러 타입을 처리하는 범용 클래스를 만들 수 있다.
class Container<T> {
private _value: T;
constructor(value: T) {
this._value = value;
}
get value(): T {
return this._value;
}
set value(newValue: T) {
this._value = newValue;
}
}
const stringContainer = new Container<string>("문자열");
console.log(stringContainer.value); // "문자열"
모든 타입이 아닌, 특정 조건을 만족하는 타입만 받도록 제한할 수 있다.
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(item: T): void {
console.log(item.length);
}
logLength("Hello"); // 문자열은 length 속성을 가짐
logLength([1, 2, 3]); // 배열도 length 속성이 있음
// logLength(42); // 숫자는 length 속성이 없으므로 오류 발생
위에서는 제네릭 타입 T가 Lengthwise 인터페이스를 반드시 만족하도록 제한했다.
함수나 클래스에 두 개 이상의 제네릭 타입 변수를 사용하여 복합적인 타입 관계를 정의할 수 있다.
function swap<K, V>(key: K, value: V): [V, K] {
return [value, key];
}
const swapped = swap("age", 30); // [30, "age"]
두 개의 타입 매개변수를 사용해 입력 순서를 바꾸거나, 타입 간의 관계를 명확하게 표현할 수 있다.
제네릭은 함수, 인터페이스, 클래스 등 다양한 구조에서 사용 가능하며, 코드의 재사용성과 유지보수성을 크게 향상시킨다.