제네릭
을 이용하면 클래스, 함수, 인터페이스를 다양한 타입으로 재사용할 수 있다.
제네릭을 사용하게 되면 선언할 때 type 파라미터(T
)만 명시하고, 사용하는 시점에 사용할 타입을 결정한다.
파라미터의 타입만 다르고 동일한 함수를 사용하고 싶을 때 활용할 수 있는 방법은 아래와 같다.
파라미터의 타입이 많지 않으면 유니언 타입으로 명시할 수 있겠지만, 어떤 타입이 올지 모르는 경우
나 3개 이상의다양한 타입이 올 수 있는 경우
에는 유니언 타입으로 작성하는 것보다 제네릭을 사용하는 것이 좋다.
function 함수<T>(파라미터: T) {
// ... 생략
}
함수<number>(파라미터)
함수<string>(파라미터)
제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다.
한번의 선언으로 다양한 타입에 재사용이 가능하다는 장점이 있다.
T
는 제네릭을 선언할 때 관용적으로 사용되는 식별자로 타입 파라미터(Type parameter)라 한다.
출처: https://poiemaweb.com/typescript-generic
<T>
→ 타입 파라미터 (일반적으로 대문자 T를 사용)
특정 타입을 전달 받아서 해당 함수에서 사용할 수 있는 것을 나타낸다.
아래의 예시와 같이 함수를 호출하는 시점에 파라미터의 타입을 결정한다.
function getSize<T>(arr: T[]): number {
return arr.length;
}
const arr1 = [1, 2, 3];
// 함수를 사용할 때 타입을 number로 결정
getSize<number>(arr1);
const arr2 = ["a", "b", "c"];
// 함수를 사용할 때 타입을 string로 결정
getSize<string>(arr2);
const arr3 = [false, true, true];
// 함수를 사용할 때 타입을 boolean로 결정
getSize<boolean>(arr3);
const arr4 = [{}, {}, { name: "Harry" }];
// 함수를 사용할 때 타입을 object로 결정
getSize<object>(arr4);
함수 위에 마우스를 올려보면 T가 설정한 타입으로 바뀌는 것을 볼 수 있다.
interface Mobile {
name: string;
price: number;
option: any;
}
위와 같이 option에 어떤 타입이 올지 모를 때 아래와 같이 any
로 타입을 지정하기보다 이런 경우에 제네릭
을 사용하는 것이 좋다.
제네릭을 사용하면 하나의 인터페이스만 선언하고 다양한 모습의 객체를 만들 수 있다.
인터페이스에서 제네릭을 사용하면 아래와 같다.
interface Mobile<T> {
name: string;
price: number;
option: T;
}
const m1: Mobile<object> = {
name: "s21",
price: 1000,
option: {
color: "red",
coupon: false,
},
};
const m2: Mobile<string> = {
name: "s20",
price: 900,
option: "good",
};
위 예시에서 option 객체의 구조가 정해져있다면 아래와 같이 작성할 수도 있다.
interface Mobile<T> {
name: string;
price: number;
option: T;
}
// option 객체의 구조가 정해져있다면 아래와 같이 작성할 수도 있다
const m1: Mobile<{ color: string; coupon: boolean }> = {
name: "s21",
price: 1000,
option: {
color: "red",
coupon: false,
},
};
const m2: Mobile<string> = {
name: "s20",
price: 900,
option: "good",
};
interface User {
name: string;
age: number;
}
interface Car {
name: string;
color: string;
}
interface Book {
price: number;
}
const user: User = { name: "a", age: 10 };
const car: Car = { name: "bmw", color: "red" };
const book: Book = { price: 4000 };
function showName<T>(data: T): string {
return data.name; // ❌ error!
}
showName(user);
showName(car);
showName(book);
이런 경우에 아래와 같이 extends
키워드를 사용해서 T를 나타내서 문제를 해결할 수 있다.
만약 전달받은 파라미터에 name이 없거나, name이 있는데 string이 아니라면 에러가 표시된다.
// <T extends { name: string }> === 전달되는 파라미터 data는 T 타입인데, 그 타입은 name이 string인 객체를 확장한 형태이다
function showName<T extends { name: string }>(data: T): string {
return data.name;
}
showName(user);
showName(car);
showName(book); // ❌ error! book에 name이 없어서 에러가 발생