타입스크립트의 제네릭은 코드의 재사용성, 타입 안전성, 유연성을 향상시키는 기능입니다. 제네릭을 사용하면 다양한 타입을 처리할 수 있는 유연한 함수, 클래스, 인터페이스를 작성할 수 있습니다.
타입 안전성 : 제네릭을 사용하면 컴파일 시점에 타입을 검사하여 런타임 오류를 줄일 수 있습니다.재사용성 : 하나의 함수나 클래스로 다양한 타입을 처리할 수 있어 코드의 재사용성이 증가합니다.유연성과 명확성 : 코드를 더 유연하게 작성할 수 있으며, 타입에 대한 정보를 명확하게 전달할 수 있습니다.복잡성 증가 : 제네릭은 코드를 복잡하게 만들 수 있으며, 읽고 이해하기 어렵게 할 수 있습니다.오용의 위험 : 잘못 사용될 경우 예상치 못한 버그를 발생시킬 수 있습니다.명확한 타입 지정 : 가능한 한 명확하게 타입을 지정하고, 제네릭 타입의 범위를 적절히 제한해야 합니다.과도한 사용 지양 : 필요 이상으로 복잡한 타입을 정의하지 않도록 주의해야 합니다.제네릭은 다음과 같은 경우에 유용합니다.
기본 형태
function func<T>(arg: T): T {
return arg;
}
const getArg<string>('getArg');
const getArg<number>(100);
위 경우 제네릭은 함수 호출부에서 타입을 정의하고, 어떠한 타입도 받을 수 있는 <T> 라는 제네릭 타입을 이용하여 작성되었습니다.
함수에 사용되는 <T> 라는 제네릭 식별자는 어떠한 문자여도 상관이 없지만 암묵적으로 T 문자열을 주로 사용합니다.
중요
타입스크립트의 제네릭은 선언부에서 타입을 지정하는 형태가 아닌, 사용하는 부분에서 타입을 지정하여 타입의 자유도를 높힐 수 있습니다. 다만 과도한 사용은 지양해야겠습니다.
Ps. 타입의 자유도를 높힐 수 있는 문법중에 함수 오버로드, 유니온 타입 지정 도 있습니다. 이들 모두의 공통점은 타입을 확장하는데 그 목적을 두고있고, 과도한 사용은 코드 복잡도와 오류를 발생시킬 수 있다는 리스크입니다.
function getSize<T>(arr: T[]): number {
return arr.length;
}
const arr1 = [true,true,false];
getSize<boolean>(arr1);
const arr2 = ["a", "b", "c"];
getSize<string | number>; //유니온, 교차 사용가능
interface Mobile<T> {
name: string;
price: number;
option: T;
}
interface IphoneOption {
color: string;
ram: number;
}
const m1: Mobile<string> = {
name: "iphone12Pro",
price: 1200,
option: "red",
};
const m2: Mobile<IphoneOption> = { //인터페이스를 제네릭으로 타입으로 지정
name: "iphone12",
price: 1000,
option: { color: "grey", ram: 512 },
};
const m3: Mobile<{ color: string; ram: number }> = {
name: "iphone15",
price: 2000,
option: { color: "white", ram: 1024 },
};
interface User {
name: string;
age: number;
}
interface Car {
name: string;
color: string;
}
interface Book {
price: number;
}
const user: User = {
name: "kth",
age: 31,
};
const car: Car = {
name: "k5",
color: "white",
};
const book: Book = {
price: 1000,
};
function showName(data: any): string {
return data.name;
}
showName(user);
showName(car);
showName(book);
실제 Book 인터페이스에는 name 속성이 없음에도 불구하고 에러를 보여주지 않습니다.
book를 출력해보면 undefined가 나옵니다.
function showName<T>(data: T): string {
return data.name; //Error : 'T' 형식에 'name' 속성이 없습니다.
}
함수 부분을 제네릭을 사용하여 변경해줍니다. 그랬더니 T 타입에 name이 있는지 없는지 추론할 수 없게되었습니다. 당연합니다 T 타입은 따로 정의하지 않았기 때문이고, 또한 data 파라미터로 들어오는 값들에 name 속성이 있을거라고 확신할 수 없기 때문입니다.
function showName<T extends {name:string}>(data: T): string {
return data.name;
}
data 는 T 타입인데, {name:string} 을 확장한 형태의 T 타입이 올것이다. 라고 명시해주었습니다.
showName(user);
showName(car);
showName(book);
// Error : 'Book' 형식의 인수는 '{ name: string; }' 형식의 매개 변수에 할당될 수 없습니다.
// 'name' 속성이 'Book' 형식에 없지만 '{ name: string; }' 형식에서 필수입니다.ts(2345)
이렇게 했더니 Book 에 정상적으로 에러가 발생하였습니다.
이상 포스팅을 마치겠습니다.