본문 바로가기
Front-End/Typescript

Polymorphism

by developerDoorold 2022. 12. 16.

문가가 공부한 기록입니다. 부정확한 내용은 편히 지적 부탁드립니다.


 

 자자 이번에는 다형성(Polymorphism)과 타입스크립트가 어떻게 다형성을 제공하는지(Generic) 알아보자~


다형성(Polymorphism)이 뭔데?

미안합니다.

 그리스어로 poly는 many, much, several, multi이다. polygon(다각형)과 같은 단어를 생각하면 된다. 그리고 morphos는 form(형태), structure(구조)란 뜻을 가지고 있다. 이것들을 조합하면 many(poly), structure(morphos)가 된다. 즉 여러 가지 다른 구조들이라는 의미다.  

 

 기본적으로 함수는 여러가지 다른 모양 또는 형태를 가지고 있다. 이번에는 타입과 관계없이 배열을 받고, 그 배열의 요소를 하나씩 출력해주는 함수를 만들어보겠다. 

 

//call signature
type SuperPrint = {
  (arr: number[]): void;
};

const superPrint: SuperPrint = (arr) => {
  arr.forEach((i) => console.log(i));
};
 첫번째 조건인 배열을 받아서, 그 배열의 요소를 각각 콘솔로 찍는 작업을 수행한다. 하지만 여기서 문제가 하나 있다. 우리가 원하는 것은 타입과 관련없이 배열을 받고 싶지만 위의 경우는 number로 배열을 받을 수 있다. 그러면 우린 무엇을 해야할까?
 
 
//call signature
type SuperPrint = {
  (arr: number[]): void;
  (arr: boolean[]): void;
  (arr: string[]): void;
};

const superPrint: SuperPrint = (arr) => {
  arr.forEach((i) => console.log(i));
};

superPrint([1, 2, 3, 4]);
superPrint([true, false, false]);
superPrint(["1", "2", "3"]);
 
call signature에 받고싶은 타입을 적어주면 된다. 하지만 이것이 좋은 해결방법일까? 다른 여러가지 타입을 추가적으로 함수에 받고싶다면 일일이 call signature에 반영해야만 하는것일까? 물론 아니다. 좋은 해결방법이 있다!!

 

//call signature
type SuperPrint = {
  (arr: number[]): void;
  (arr: boolean[]): void;
  (arr: string[]): void;
};
 
 concrete type이란 우리가 전부터 봐왔던 타입을 의미한다. number, boolean, string, void, unknown 이것들이 concrete type이다. 여기서 우리는 타입스크립트한테 generic 타입을 받을것이라고 알려줄건데 generic이란, 타입의 placeholder 같은 것이며 concrete type을 대신해서 사용할 수 있다. 즉 우리는 타입스크립트로 placeholder를 작성할 수 있고 그게 무엇인지 추론해서 함수를 사용하는 것이다. 
 
// 에러가 발생한다.
superPrint([1, 2, true, false)];
위의 코드는 동작하지 않는다. 그 이유는 위와같은 arguments가 call signature에 기술되지 않았기 때문이다. 
 
//call signature
type SuperPrint = {
  (arr: number[]): void;
  (arr: boolean[]): void;
  (arr: string[]): void;
  (arr: (number | boolean | string)[]): void;
};

 

이렇게 적어주면 안되냐고?? 킹받게 하지마라... 앞으로 함수의 인자를 미리 알수 없는 경우에는 generic를 사용해보자!
 
//call signature
type SuperPrint = {
  <TypePlaceHolder>(arr: TypePlaceHolder[]): void;
};

const superPrint: SuperPrint = (arr) => {
  arr.forEach((i) => console.log(i));
};

// 아무런 문제가 발생하지 않는다.
superPrint([1, 2, 3, 4]);
superPrint([true, false, false]);
superPrint(["1", "2", "3"]);
superPrint([1, 2, true, false]);

<> 꺽쇠안에 들어가는 문자는 아무것이나 들어가도 된다.(ex: <Muyaho>)

타입스크립트가 배열의 요소에 맞게 타입을 추론해서 알아버리는 놀라운 장면

 

generic을 통해서 call signature안에 받을 인자의 타입을 일일이 노가다하지 않아도 되는 것을 알게 됐으니 이번에는 함수의 리턴 값 또한 generic 하게 바꿔보자. 

//call signature
type SuperPrint = {
  <TypePlaceHolder>(arr: TypePlaceHolder[]): TypePlaceHolder;
};

// 인자로 받은 배열의 첫번째 요소를 리턴
const superPrint: SuperPrint = (arr) => arr[0];

const a = superPrint([1, 2, 3, 4]);
const b = superPrint([true, false, false]);
const c = superPrint(["1", "2", "3"]);
const d = superPrint([1, 2, true, false, "무야호"]);
console.log(a, b, c, d); // 1 true "1" 1

이것이 바로 generic이고 다형성이다. 가슴이 웅장해진다.....

 

+참고자료

https://nomadcoders.co/typescript-for-beginners/lectures/3675

댓글