[TypeScript] 제네릭

해리포터·2023년 1월 12일
0
post-thumbnail

제네릭(Generic)

제네릭 을 이용하면 클래스, 함수, 인터페이스를 다양한 타입으로 재사용할 수 있다.
제네릭을 사용하게 되면 선언할 때 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);
위 사진과 같이 모든 파라미터에 name이 있다고 확신할 수 없어서 타입스크립트는 에러를 발생시킨다.

extends 키워드

이런 경우에 아래와 같이 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이 없어서 에러가 발생

References

TypeScript #7 제네릭 Generics

TypeScript - Generic | PoiemaWeb

profile
FE Developer 매일 한 걸음씩!

0개의 댓글