poly = many, multi, several ...
morpho = structure, ...
many different structure
첫번째 call signature을 만들어보자.
type SuperPrint = {
(arr: number[]):void
}
SuperPrint는 number로 된 arr를 인자로 받고 void를 return한다.
type SuperPrint = {
(arr: number[]):void
}
const superPrint: SuperPrint = (arr) => {
arr.forEach(i => console.log(i))
}
이렇게 인자로 들어올 배열의 요소들을 print하는 call signature이다.
type SuperPrint = {
(arr: number[]):void
(arr: boolean[]):void
}
const superPrint: SuperPrint = (arr) => {
arr.forEach(i => console.log(i))
}
superPrint([1, 2, 3, 4])
superPrint([true, false, true])
이렇게 call signature에 대한 인자 타입을 하나 더 지정해줌으로써 최하단의 superPrint함수 두개가 정상적으로 동작하게 되었다.
superPrint(["a", "b", "c"])
하지만 이렇게 문자열로 이루어진 배열이 인자로 들어간 함수는 정상동작하지 않는다.
그렇다면 위의 과정과 같이
(arr: string[]):void
를 추가해줘야 할까?
이론상으로는 이렇게 하면 정상적으로 동작하지만 정답은 아니다.
다형성을 활용하는 더 좋은 방법이 있다.
내가 하고 싶은건 인자로 들어갈 배열의 요소로 어떤 타입의 데이터가 포함되어 있어도 정상적으로 동작하게끔 만드는 것이다.
(arr: (number|boolean)[]):void
이런식으로 인자에 대해서 처리하는 것도 완전한 방법은 아니다. 인자로 들어갈 데이터의 타입에 대해 모든 경우의 수에 대해서 고려해야만 하기 때문이다.
그래서 그 대신 'generic'을 활용할 필요가 있다.
generic은 타입의 placeholder 같은 것이다.
다시 말해서 generic을 사용하는 이유는 call signature를 작성할 때, 변수에 대한 타입을 확신할 수 없을 때에도 함수가 동작하게끔 만들기 위해서다. 어떤 타입에 대해서도 일단 동작하게 만드는 것이다.
type SuperPrint = {
<TypePlaceholder>(arr: TypePlaceholder[]): void
}
여기서 <>안에 들어가는 것이 이 generic에 대한 이름이 된다.
type SuperPrint = {
<TypePlaceholder>(arr: TypePlaceholder[]): void
}
const superPrint: SuperPrint = (arr) => {
arr.forEach(i => console.log(i))
}
superPrint([1, 2, 3, 4])
superPrint([true, false, true])
superPrint(["a", "b", "c"])
superPrint([1, 2, true, false])
여기서 return값에 대한 타입을 바꿔줄 수도 있다.
type SuperPrint = {
<TypePlaceholder>(arr: TypePlaceholder[]): TypePlaceholder
}
const superPrint: SuperPrint = (arr) => arr[0]
const a = superPrint([1, 2, 3, 4])
const b = superPrint([true, false, true])
const c = superPrin(["a", "b", "c"])
const d = superPrint([1, 2, true, false])
여기서 TypePlaceholder는 단순히 이름일 뿐이므로 일반적으로 T나 V로 짧게 사용하는 경우가 많다.
요는 <>안에 placeholder로서의 generic의 이름을 씀으로써 확정되지 않은 타입에 대한 call signature를 작성할 수 있게 되는 것이다.