다형성. 말 그대로 Typescript의 인자나 값들은 다양한 Type을 가질 수 있다.
제네릭은 C#이나 Java와 같은 언어에서 재사용 가능한 컴포넌트를 만들기 위해 사용하는 기법입니다. 단일 타입이 아닌 다양한 타입에서 작동할 수 있는 컴포넌트를 생성할 수 있습니다.
(구체적인 타입을 지정하지 않고 다양한 인수와 리턴 값에 대한 타입을 처리할 수 있다.)
타입스크립트에서 제네릭을 통해 인터페이스, 함수 등의 재사용성을 높일 수 있습니다.
type SuperPrint = <T>(a: T[]) => T
// -> T는 배열에서 오고, 함수의 첫번째 파라미터에서 온다.
// <꺽쇠>를 사용해 TS가 유추한 타입으로 대체할 수 있다.
// 많은 타입을 동시에 가지게 할 수 있음!
const superPrint: SuperPrint = (a) => a[0]
const a = superPrint([1, 2, 3, 4]) // a: number
const b = superPrint([true, false, true, false]) // b: boolean
const c = superPrint(['a', 'b', 'c']) // c: string
const d = superPrint([1, 2, true, false]) // d: number | boolean
// 함수마다 다른 타입의 값이 들어가고 있음에도 오류가 없다!
여기서 사용한 <T>
를 제네릭<Generic>
이라고 하고, 제네릭을 사용하면 타입스트립트가 이 함수의 call signature를 만들어주기 때문에 보호받을 수 있다.
제네릭은 사용자가 요구한 대로 call signature를 생성해줄 수 있는 도구이다.
type SuperPrint = {
(arr: any[]): any
}
const superPrint: SuperPrint = (arr) => arr[0]
let a = superPrint([1, "b", true]);
// pass
a.toUpperCase();
any를 사용하게 되면 제네릭처럼 보호받을 수 없기 때문에(call signature: any로 지정) 사용을 지양하자!
type SuperPrint = <T,M>(a: T[], b: M) => T
// -> M이라는 제네릭이 추가됐을 때
const superPrint: SuperPrint = (a) => a[0]
const a = superPrint([1, 2, 3, 4], "X") // (a: number[], b: string) => number
const b = superPrint([true, false, true, false], 1) // (a: boolean[], b: number) => boolean
const c = superPrint(['a', 'b', 'c'], false) // (a: string[], b: boolean) => string
const d = superPrint([1, 2, true, false], "TS") // (a: (number | boolean)[], b: string) => number | boolean
타입스크립트는 제네릭을 처음 인식했을 때와 제네릭의 순서를 기반으로 제네릭의 타입을 알게 된다. 따라서 위와 같이 복수의 제네릭을 사용할 수도 있다!
type Player<E> = {
name: string
extraInfo: E
} // 제네릭<E>을 가진 Player 객체의 형태, extraInfo에 제네릭이 들어가고 있다.
type NicoExtra = {
favFood:string
} // Player.extraInfo에 들어갈 타입
type NicoPlayer = Player<NicoExtra>
// Player<E>와 같은 형태
const nico: NicoPlayer = {
name:"nico",
extraInfo: {
favFood: "kimchi"
}
} // nico에 NicoPlayer 타입을 지정해 한번에 모든 타입 지정이 완료된 모습
useState() // TS는 state의 타입을 알 수 없음
->
useState<number>() // useState가 number 타입을 가질 경우