
polymorphism이라고도 불리며 ts에서 다형성이란 여러 타입을 가져 어러 형태를 가짐을 의미한다.
type Print = {
(arr : number[]) : void;
(arr : string[]) : void;
}
const arrFunc : Print = (arr) => arr[0]
const result1 =arrFunc([1,2,3]);
const result2 =arrFunc(['1','2','3']);
우리는 하나의 함수에 다양한 call signature가 존재하게 해 오버로딩 됨으로서 함수가 다형성을 가짐을 알 수 있다.
그러나 우리는 이런 생각을 할 수 있다.
만약 새로운 call signature가 필요할 경우 계속 추가해서 적용시켜야 할까?
물론 그럴 수 있다.
type Print = {
(arr : number[]) : void;
(arr : string[]) : void;
(arr : (string |number)[]) : void;
(arr : (string| number |boolean)[]) : void;
}
const arrFunc : Print = (arr) => arr[0]
const result1 =arrFunc([1,2,3]);
const result2 =arrFunc(['1','2','3']);
const result3 =arrFunc([1,'2']);
const result4 =arrFunc([1,'2',false]);
물론 가능은 하지만 굉장한 노가다에 효율적이지 못한 코드들이라고 할 수 있다..
이를 해결하기위해 나온 타입이 generic type 이다.
제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여,
단일 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다.
type Print = <T>(arr : T[]) => void
const arrFunc : Print = (arr) => arr[0]
const result1 =arrFunc([1,2,3]);
const result2 =arrFunc(['1','2','3']);
const result3 =arrFunc([1,'2']);
const result4 =arrFunc([1,'2',false]);
즉 generic type은 타입에 맞는 call signature을 넣을 필요 없이 함수 호출시 필요한 타입을 알아서 정해준다.
꺽새(<>)안에 오는 식별자는 어떤 값을 넣어도 상관이 없으며 나중에 타입이 들어올 자리이다.
any를 쓰면 TS의 보호장치에서 벗어나기 때문에 에러를 잡아내기 힘들다.
따라서 generic을 사용해 코드에 따른 타입을 보장받는 것이 좋다.
any를 사용할 경우
type Print = (arr : any[]) => any
const arrFunc : Print = (arr) => arr[0]
const result1 =arrFunc([1,2,3]);
const result2 =arrFunc(['1','2','3']);
const result3 =arrFunc([1,'2']);
const result4 =arrFunc([1,'2',false]);
console.log(result1.toUpperCase()); // 런타임시 에러발생
result1의 값은 숫자 1이라 toUpperCase() 메서드는 적용될 수 없지만 타입을 any로 지정해놨기 때문에
컴파일시 에러가 발생하지 않는 문제가 발생한다.
generic 타입은 인자의 갯수에 따라 복수로 사용할 수 있다.
type Print = <T,M>(arr : T[], b: M ) => T
const arrFunc : Print = (arr) => arr[0]
const a= arrFunc([1,2,3,4],'one');
const b= arrFunc(['1','2','3'],1);
const c= arrFunc([false,true,false,false],true)
const d= arrFunc([1,2,'3',false],{});
generic type은 call signature에서도 쓰일 수 있지만 함수에서도 사용할 수 있다.
function arrFunc3<T>(arr:T[]){
return arr[0]
}
const a= arrFunc3([1,2,3,4]);
const b= arrFunc3(['1','2','3']);
const c= arrFunc3([false,true,false,false])
const d= arrFunc3([1,2,'3',false]);
const arrFunc2 = <T>(arr: T[]) => arr[0]
const a= arrFunc2([1,2,3,4]);
const b= arrFunc2(['1','2','3']);
const c= arrFunc2([false,true,false,false])
const d= arrFunc2([1,2,'3',false]);
제네릭 타입을 이용하여 원하는대로 코드 확장 및 타입 생성,
다른 타입 속으로 넣어서도 사용이 가능하다.
type Player<E> ={
name: string,
extraInfo :E,
}
type ExtraInfo = {
favFood :string
}
const hw : Player<ExtraInfo> = {
name: 'hyunwoo',
extraInfo: {
favFood :'gogi'
}
}
const vain : Player<string> = {
name: 'vain',
extraInfo : 'ad'
}
Player 객체 타입의 extraInfo에 대한 타입이 정해지지 않았을때 generic을 사용해 생성시점으로 미룰 수 있다.
이렇게하면 Player 타입을 가지는 변수들이 좀더 유연하게 타입을 선언할 수 있다.