[TypeScript] #02. Functions

ZenTechie·2023년 5월 18일
0

TypeScript

목록 보기
2/3
post-thumbnail

Call Signatures

Call Signatures라는 것은 함수 위에 마우스를 올렸을 때 보게 되는 것을 의미한다.
이는 함수를 어떻게 구현하는지를 알려주는게 아닌, 함수의 타입인자의 타입함수의 반환 타입을 알려준다.

코드로 살펴보자.

function add(a : number, b : number) {
  return a + b
}

const add = (a:number, b:number) => a + b

위 코드의 Call Signautresconst add: (a: number, b: number) => number 이다.

자 그러면 우리만의 add 함수의 Call Signature는 어떻게 선언할 수 있을까?

type Add = (a:number, b:number) => number

이렇게 만들 수 있다!
그리고 add 함수는 다음과 같이 변경한다.

const add: Add = (a, b) => a + b

이렇게 되면 우리는 인자의 타입을 명시하지 않아도 된다. 이미 Call Signature선언했기 때문이다.

이로써 우리는 함수를 구현하기 전에, 타입을 설명할 수 있고 함수가 어떻게 작동하는지 선언해둘 수 있다.

Overloading(오버로딩)

오버로딩은 함수가 여러개의 Call Signatures를 가지고 있을 때 발생한다. 그냥 여러 개가 아니라 서로 다른 여러 개의 Call Signatures를 가졌을 때를 의미한다.

Call Signature는 모습이 서로 다를 수 있다.

  1. 매개변수의 데이터 타입이 다른 경우
  2. 매개변수의 개수가 다른 경우

만약 인자의 타입이 다른 Call Signature가 있다면, 다음과 같이 작성할 수도 있다.

type Add = {
  (a:number, b:number) : number,
  (a:number, b:string) : number
}

이때는 매개변수의 데이터 타입이 다르기 때문에 예외 처리를 해줘야 한다.

const add:Add = (a, b) => {
  if (typeof b === "string") return a
  return a + b

만약, 인자의 개수가 다른 Call Signature를 가지고 있다면 아래와 같이 추가하면 된다.

type Add = {
  (a:number, b:number) : number,
  (a:number, b:number, c:number) : number
}

여기서 다음과 같이 Add함수를 호출한다고 가정해보자.

const add:Add = (a, b, c) => {
  return a + b
}

이렇게 작성하면 에러가 발생한다. 왜냐하면 Call Signatures에 c를 인자로 가지지 않는 Signature가 있기 때문이다. 즉, Call Signature들 사이의 파라미터의 개수가 다르기 때문이다.

위의 Call Signatures를 살펴보면, c하나의 옵션을 의미한다.(있을 때도 있고 없을 때도 있으니)
따라서, 다음과 같이 변경해서 호출해야 한다.

const add:Add = (a, b, c?:number) => {
  if (c) return a + b + c
  return a + b
}

c추가적으로 타입을 줘야하고, 이 파라미터는 선택사항임을 알려주는 것이다.

즉, Overloading은 다수의 Call Signatures가 존재할 때 발생하는 현상이다.

Polymorphism(다형성)

poly란? many, serveral, much, multi 같은 뜻
morphos란? form, structure 같은 뜻
Polymorphos란? poly + morphos여러 다른 구조

즉, Polymorphism다양한 형태를 가지는 특성, 다형성을 의미한다.

SuperPrint라는 Call Signatures가 있고 인자로 정수 배열을 받고 각 원소들을 출력한다(void)고 가정하자.

type SuperPrint = {
  (arr: number[]):void
}

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

근데, 꼭 정수가 아닌 다양한 타입을 가지는 배열을 받고 싶을땐 어떻게 할까?

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,true,false])
superPrint(["1","2","3"]) // Error

위와 같이 boolean[]을 타입으로 갖는 Call Signature추가하면 된다.
그러나, string 배열을 타입으로 갖는 Call Signature는 추가하지 않았기 때문에 호출 시 에러가 발생한다.

그렇다면, 다른 Call Signature또 추가해야 하는 것일까? 원하는 Call Signature를 그때마다 계속해서 추가해야 하므로 이렇게 하면 너무 비효율적이다.

다형성 만들기 - Generic Type 사용

자, 우리가 Call Signature에서 사용한 타입은 concrete type이다.
concrete type이란? number, string, boolean, void, unknown

이를 generic type으로 바꾸면 우리는 매우 효율적으로 코드를 작성할 수 있게된다.
generic type이란? generic은 타입의 placeholder같은 것

따라서, generic type대신해서 사용하면 된다.
이렇게 되면 TS어떤 타입인지 추론하고 사용한다.
즉, Call Signature확실한 타입을 모를 때 Generic을 사용한다.

그러면 Generic Type으로 바꿔보자.

type SuperPrint = {
  <T, V>(arr: <T, V>[]):void
}

먼저 앞에 <T, V>를 추가해주고 인자의 타입똑같이 <T, V>로 변경해준다.
[❗️단, 여기서 꼭 <T, V>가 아니어도 상관없다! (원하는 이름을 사용해도 무방하다.)]

그리고 만약, return 타입도 설정하고 싶다면 똑같이 <T, V>로 변경하면 된다.
(이 역시도 꼭 <T, V>가 아니어도 된다.)

❗️❗️ 여기서 중요한 것은 어떤 이름을 사용해도 상관없지만,
모두 똑같은 이름을 사용해야 한다는 것이다.

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

결론은, Generic Type을 사용하면 TS는 내부적으로 유추한 타입으로 Call Signature를 우리에게 알려준다. 그리고 인자의 개수가 달라도 상관없고 어떠한 타입을 넣어도 상관없다.

이게 바로 다형성이다.(SuperPrint가 다양한 형태를 갖는다.)

Generic Recap.

그렇다면 Generic 대신 그냥 any를 사용하면 되지 않을까? 결론적으로 any는 사용하면 안된다.

코드를 통해 살펴보자.

type SuperPrint = (arr: any[]):any

const superPrint : SuperPrint = (a) => a[0]

const d = superPrint([1, 2, false, "String"])

console.log(d.toUpperCase())

superPrint는 인자로 들어온 배열의 첫번째 요소를 반환한다.
dsuperPrint를 호출하는데 인자로 들어온 배열의 첫번째 요소가 number, 즉 1이다.

toUpperCase()string에서만 호출가능한 함수이므로 에러가 발생해야 하지만, 에러가 발생하지 않는다. 즉, 에러에 대한 보호를 전혀 받을 수가 없다.

이것이 Generic대신 any를 사용하면 안되는 이유이다.

그렇다면, 다수의 Generic을 사용하려면 어떻게 해야될까?
인자를 하나 더 추가하고 해당 인자의 타입을 다르게 설정하고 싶다고 가정하자.

매우 쉽게 ','추가함으로써 만들 수 있다.

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

PlaceholderCall Signature요구하는대로 생성한다.
결국, Generic개발자가 요구한대로 Call Signature를 생성해주는 도구이다.
TSGeneric처음 인식했을 때Generic의 순서기반으로 Generic의 타입을 추론한다.

'제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다.'

profile
데브코스 진행 중.. ~ 2024.03

0개의 댓글