제네릭은 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징이다. 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용된다.
// 기본 문법
function getText <T>(text: T): T {
return text;
}
// <T>는 placeholder이다.
// 함수 호출할 때 함수 안에서 사용할 타입을 넘겨준다.
getText<string>('hi');
getText<number>(10);
getText<boolean>(true);
// string으로 호출 됐을 때 동작
function getText <string>(text: string): string {
return text;
}
제네릭은 내가 요구한 대로 signature를 생성해줄 수 있는 도구이다.
type SuperPrint = <T, M>(a: T[], b: M) => T
const superPrint: SuperPrint = (a, b) => a[0]
superPrint([1, 2, true, false, "hello"], "bye")
타입스크립트는 제네릭을 처음 인식했을 때와 제네릭의 순서를 기반으로 제네릭의 타입을 알게된다.
type SuperPrint = (a: any[]) => any
const superPrint: SuperPrint = (a) => a[0]
const d = superPrint([1, 2, true, false, "hello"])
d.toUpperCase() // 에러나지 않음.
any를 쓰면 어떤 타입이 와도 무관하기 때문에 toUpperCase
에 에러를 잡아내지 못한다. 제네릭을 사용할 때 타입스크립트가 해당 함수의 call signature
를 만들어주기 때문에 에러를 발생시킬 수 있다. 즉, 보호를 받을 수 있다.
// type SuperPrint = <T, M>(a: T[], b: M) => T
// const superPrint: SuperPrint = (a, b) => a[0]
// 다른 모양
function superPrint<T>(a: T[]) {
return a[0]
}
제네릭은 타입을 생성할 수도 있고 타입을 확장할 수도 있다.
type Player<E> = {
name: string
extraInfo: E
}
type NicoExtra = {
favFood: string
}
type NicoPlayer = Player<NicoExtra>
const nico: NicoPlayer = {
name: "nico"
extraInfo: {
favFood: "ramen"
}
}
타입을 생성하고 그 타입을 또다른 타입에 넣어서 사용할 수 있다. Player
타입에서 하나가 달라질 수 있는 타입이라면 그게 E
이고 E
에 제네릭을 넣어서 사용한다. 그럼 Player
타입을 재활용할 수 있는 기회를 많이 가진다.