[TIL] 230112

먼지·2023년 1월 12일
0

TIL

목록 보기
49/57
post-thumbnail


TypeScript Functions

Typescript로 블록체인 만들기

Call Signatures

Overloading

Polymorphism 다형성

정의

  • poly : 그리스어로 many, several, much, multi 많은, 다수의
  • polygon : 다각형 (gon : 각도) 여러개의 각, 여러 개의 면을 가지고 있음
  • morphous, morphic : form(형태), structure(구조) 모양
    poly(many) + morphous(structure) => 여러가지 다른 구조들 모양들 형태들

기본적으로 함수는 여러가지 다른 모양 또는 다른 형태를 가지고 있음 (여러 parameter를 가질 수 있음)

제네릭!

예시

배열을 받고, 그 배열의 결과를 print 해주는 함수

// 세 개의 call signature가 있음. 
// 근데 number, string이 섞인 배열을 받고 싶으면 또 시그니처를 작성해야 함..
type SuperPrint = {
  (arr: number[]): void;
  (arr: boolean[]): void;
  (arr: string[]): void;
  // (arr: number|string): void;
}

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

superPrint([1, 2, 3, 4]);
superPrint([true, false, true]);
superPrint(["a", "b", "c"]);

다형성을 활용하는 더 좋은 방법이 있음. 타입스크립트에게 더 나은 방법으로 얘기해줄 수 있음!

  • concrete type : string, number, boolean, unknown, void 등
  • generic type : 타입의 placeholder 같은 것. 타입스크립트가 그게 뭔지 유추 추론해서 사용함.

어떤 배열이든 프린트가 잘 동작되게 만들기. 모든 가능성을 다 조합해서 만드는 방법은 별로 좋지 않음. call signature를 작성할 때 여기 들어올 확실한 타입을 모를 때 generic을 사용함. (concrete type이 되겠지만 그 타입을 미리 알 수 없을 때)

type SuperPrint = {
  // 이 argument가 제네릭을 사용할거다 받는다! 라고 알려주기
  // 보통 T, V 알파벳으로 많이 작성함
  // => 이 call signature가 제네릭을 받는다라는 걸 알려주는 방법
  <TypePlaceholder>(arr: TypePlaceholder[]): void;
}

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

// 모두 문제없이 해결
superPrint([1, 2, 3, 4]); // number 타입의 배열로 동작하구나!
superPrint([true, false, true]);
// 마우스를 올려보면 > const superPrint: <boolean>(arr: boolean[]) => void
// superPrint 함수에 boolean 타입이 주어졌군

superPrint(["a", "b", "c"]);
superPrint([1, 2, true, 'ㅁ']); 
// <string|number|boolean>(arr: (string|number|boolean[]) => ...

타입스크립트는 값들을 보고 타입을 유추하고, 기본적으로 그 유추한 타입으로 call signature를 보여줌.

리턴 타입 바꾸기! superPrint는 arr를 받고, 그 배열의 첫 번째 요소를 리턴.

type SuperPrint = {
  <T>(arr: T[]): T;
}

const superPrint: SuperPrint = (arr) => arr[0];

const a = superPrint([1, 2, 3, 4]); // : number
const b = superPrint([1, 2, true, 'ㅁ']); // : string | number | boolean

정리

  • 함수의 call signature를 입력할 때, 우린 placeholder를 사용함 = 그리고 이게 바로 다형성(polymorphism) = 타입스크립트에게 타입을 유추하도록 알려준 것 = 제네릭(generic)
  • polymorphism : 인자들과 반환값에 대하여 형태(타입)에 따라 그에 상응하는 형태(타입)를 갖을 수 있다.
  • any와의 차이점은 해당 타입에 대한 정보를 잃지 않는다. any는 any로서 밖에 알 수 없지만 generics는 타입 정보를 알 수 있다.

Generics Recap

직접 모든 call signature를 작성하는 것은 별로 좋은 생각이 아님. 바로 제네릭을 사용해야 할 때! 제네릭은 기본적으로 placeholder를 사용해 우리가 작성한 코드의 타입 기준으로 바꿔줌
=> 타입스크립트가 우리 코드를 보고 알아냄. 그리고 placeholder를 발견한 타입으로 대체

any를 쓰지 않는 이유

만약 함수의 목표가 아무거나 넣어도 아무거나 리턴되는 것이라면 any를 넣는 게 나을 거라 생각할 수도 있음. 이건 우릴 더 이상 지켜주지 않음. 타입스크립트로부터 보호받을 수 없음.

https://www.typescriptlang.org/docs/handbook/2/generics.html#hello-world-of-generics

https://stackoverflow.com/questions/44023061/what-are-the-difference-between-generic-typet-vs-any-in-typescript

짧게 요약하자면, 'any'를 사용하는 것은 어떤 타입이든 받을 수 있다는 점에서 'generics'과 같지만 함수를 반환하는데 있어 'any'는 받았던 인수들의 타입을 활용하지 못한다. 즉, 'generics'은 어떤 타입이든 받을 수 있다는 점에서 'any'와 같지만 해당 정보를 잃지 않고 타입에 대한 정보를 다른 쪽으로 전달할 수 있다는 점이 다르다

  • 제네릭은 네가 요구한 대로 signature 를 생성해줄 수 있는 도구
  • 우리가 하는 요청에 따라 call signature를 생성한단느 뜻

superPrint타입 제네릭을 하나 더 추가하고 싶다면?
제일 먼저 해야할 일은 제네릭을 사용할거라고 얘기하고 이름을 작성하고, 이 제네릭을 어디에서 사용할 건지 얘기하기

type SuperPrint = <T, V>(a: T[], b: V): T

Conclusions

라이브러리를 만들거나, 다른 개발자가 사용할 기능을 개발하는 경우엔 제네릭이 유용할 거임. 그 외 대부분의 경우엔 직접 작성할 일은 없음

다른 방법으로 제네릭 선언 - 일반 함수로 대체

function superPrint<V>(a: V[]) {
  return a[0];
}


const a = superPrint<boolean>([1, 2, 3, 4]); // 빨간줄 에러
// 타입스크립트에 boolean이라고 명시해주고 있는 것. 꼭말해줄필요X
// => 항상 타입스크립트가 스스로 타입을 유추하도록 하는 것이 좋음
// 타입스크립트는 똑똑함!

제네릭을 사용하는 또다른 경우

  • 타입을 생성
  • 타입을 확장
type Player<E> = {
  name: string
  extreInfo: E
}

const nico: Player<{ favFood: string }> = {
  name: 'nico',
  extraInfo: {
  	favFood: 'kimchi;
  }
}

// =

type NicoPlayer = Player<{ favFood: string }>
const nico: NicoPlayer = {
  name: 'nico',
  extraInfo: {
  	favFood: 'kimchi;
  }
}

// 원하는대로 코드를 확장
// 타입을 생성하고 그 타입을 또다른 타입에 넣어서 사용
type NicoExtra = { favFood: string }
type NicoPlayer = Player<NicoExtra>

많은 것들이 있는 큰 타입을 하나 가지고 있는데, 그 중 하나가 달라질 수 있는 타입이라면 제네릭을 넣기. 그럼 많은 재사용이 가능.

또 제네릭은 함수에서만 쓰이는 게 아니라 정말정말 많은 곳서 쓰임. 예를 들면, 대부분의 기본적인 타입스크립트의 타입은 제네릭으로 만들어져 있음.

Array를 생성하는데 제네릭을 받고 있음.

type A = Array<number>

let a: A = [1, 2, 3, 4]

number[] === Array<number>
profile
꾸준히 자유롭게 즐겁게

0개의 댓글