Pandaman Blog

[Typescript] 함수 본문

Front end/Typescript

[Typescript] 함수

oyg0420 2021. 12. 19. 22:30

함수

목표

함수를 설명하는 타입을 작성하는 방법에 대해 알아보고, 실제로 어떻게 적용하면 효과적인지 알아보자.

호출 시그니쳐

타입스크립트의 함수 타입 문법을 호출 시그니처라고 부른다. 그렇다면 함수 타입 문법은 무엇일까? 우리가 흔히 사용하고 있다.

예를 들어 아래와 같이 sum이라는 함수가 있다고 가정하자.

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

이걸 타입 문법으로 변경하면? 아래와 같이 표현할 수 있다.

(a: number, b: number) => number;

(a: number, b: number): number;

매우 쉽다. 화살표 함수와 매우 유사하다.
함수에 함수를 인수로 전달하거나, React에서는 컴포넌트의 props로 함수를 전달하거나 등등 이 문법을 사용할 수 있다.

타입, 인터페이스 별칭으로 한정할 수 있는 독립 호출 시그니쳐를 추출해보자.

// greeter(name: string)
type Greeter = (name: string) => string; // 1

type Greeter = {
    (name: string): string; // 2
    from: string;
} 

interface Greeter {
    (name: string): string; // 3 
    from: string;
}

뭐 여러가지 방법으로 호출 시그니쳐를 표현 할 수 있다. 첫번째는 간단한 함수를 나타낸 경우 1번처럼 단축형 시그니처를 사용하면 된다. 좀더 복잡한 경우는 2, 3 번과 같이 전체형 시그니처를 사용하면 될 것 같다. 2번과 3번의 차이는 단지 type, interface 의 차이이다.

또한, 우리는 타입을 미리 설계하고 함수를 구현해도 된다.

type Greeter = (name: string) => string;

const greeter: Greeter = (name) => {
    return `hello, ${name}!`;
}

Greeter라는 독립 함수 시그니처를 정의했다. 그리고 함수 표현식으로 greeter를 선언하면서 Greeter 타입임을 명시했다. 인자인 name에는 따로 string을 명시하지 않아도 된다. Greeter 타입에 명시된 인자의 타입으로 추론된다.

Optional Parameter

JavaScript의 함수는 종종 가변적인 수의 인수를 취한다.

아래 예제를 살펴보자.

function f(n: number) {
  console.log(n.toFixed()); // 0 arguments
  console.log(n.toFixed(3)); // 1 argument
}

function f(x?: number) {
  // ...
}
f(); // OK
f(10); // OK

이때 우리는 ? 를 사용하여 매개변수을 옵셔널하게 사용가능하다. x 의 타입은? undefied | number 가 된다.

Function Overloads

오버로딩이란 같은 이름의 메서드 여러개를 가지면서 매개변수의 유형과 개수가 다르도록 하는 기술이다.
타입스크립트의 함수 오버로딩이란 무엇일까? 타입스크립트의 함수 오버로딩이란 함수 시그니처가 여러개인 함수라고 정의할 수 있다.

함수 오버로딩을 하기 위해서는 함수 시그니처 정의와 구현이 필요하다.

함수 시그니처 정의라는 것은 어떤 파라미터 타입들을 다룰 것인지 선언하는것이며, 구현은 각 파라미터 타입에 대응하는 구체적인 코드를 작성하는 것이며 단 하나의 구현을 갖을 수 있다. 즉, 오버로딩을 통해서 여러 형태의 함수 시그니처를 정의할 수 있지만, 실제 구현은 한 번만 가능하므로 여러 타입에 대한 분기는 함수 본문 내에서 이루어져야 한다.

타입스크립트에서는 시그니쳐를 작성하여 다양한 방식으로 호출할 수 있는 함수를 지정할 수 있다.

// 함수 시그니처 정의
function introduction(name: string, age: number): string;
function introduction(name: string): string;

// 함수 구현
function introduction(name: string, age?: number) {
  if (age) {
     return `my name is ${name}, I'm ${age}years old.`
  } else {
    return `my name is ${name}.`
  }
}


introduction('Coo', 33);
introduction('Coo');

사실 이 함수를 보면 타입스크립트의 함수 오버로딩이 왜 필요한지 잘 모르겠다고 생각이 들었다.

이제부터 왜 함수 오버로딩이 필요한지에 대한 예제를 살펴보자.

링크

function getID(qty?: number): number | number[] {
  if (!qty) {
    return Math.random();
  }

  const ids = [];
  for(let i = 0; i < qty; i++) {
    ids.push(Math.random());
  }

  return ids;
}

let id: number;
id = getID()

getId는 수량(qty)를 인자로 하고 수량이 없는 경우 number 타입을 반환하고 수량이 있다면 number[] 타입을 반환하는 함수이다.
마지막 라인에서 getID()를 선언하고 id에 할당하면 어떻게 될까? 에러가 발생한다. getID() 는 number | number[] 타입을 반환하기 때문이다.
이때 필요한건 뭐? 바로 함수 오버로딩이다.

우리는 의도한대로 명확한 반환 타입을 얻고 싶다

이제 함수의 오버로드 시그니쳐가 필요하다.
링크

function getID(): number;
function getID(qty: number): number[];


function getID(qty?: number) {
  if (!qty) {
    return Math.random();
  }

  const ids = [];
  for(let i = 0; i < qty; i++) {
    ids.push(Math.random());
  }

  return ids;
}

let id: number;

id = getID()

우리는 함수 오버로딩을 통해 이 문제를 해결했다. 인자에 따라 반환타입이 다른 두개의 타입 시그니처를 정의했다.
매개변수가 없는 getID는 number 타입을 반환하는 시그니처, 매개변수가 number인 getID는 number[] 타입인 시그니처가 정의되었다. 마지막 라인인 id = getID() 는 매개변수가 없으므로 number타입이 반환되어 문제를 해결했다.

아 이런 경우 필요하구나! 라는 것을 깨달았다.

동일한 함수지만 매개변수에 따른 의도한 값의 타입을 얻고 싶을때 사용하면 참 좋겠구나 라고 생각이 들었다. 위 예제처럼 말이다.

그렇다면 이 경우를 제외하고는 함수 오버로딩이 필요없을까?
링크

function getLength(data: string): number;
function getLength(data: any[]): number;

function getLength(data: string | any[]) {
  return data.length;
}

내생각은 잘 모르겠다. 그렇지만 우리가 해당 함수 타입을 확인할때 명확해진다는 점.

Function Type

void

void는 명시적으로 아무것도 반환하지 않은 함수의 반환타입니다.

function test() {
  const a = 1 + 2;
}

function test1() {
  console.log('this is test')
}

never

함수의 리턴 타입으로 never가 사용될 경우, 오류를 출력하거나 리턴 값을 절대로 내보내지 않음(무한 루프(loop))

function fail(msg: string): never {
  throw new Error(msg);
}

function e(): never {
  while(true){
   console.log('never');
  }
}

unknown

unknown 타입은 모든 값을 나타낸다. 이것은 any 타입과 유사하지만 unknown 타입으로 작업을 수행하는 것이 합법적이지 않기 때문에 더 안전하다. 즉 에러를 발생시키기때문에 안전하다.

function f1(a: any) {
  a.b(); // OK
}
function f2(a: unknown) {
  a.b();
Object is of type 'unknown'.
}

object

특수 타입인 object는 원시타입이 아닌 타입을 의미한다.(string, number, bigint, boolean, symbol, null, or undefined)

Function

JavaScript의 모든 함수 값에 존재하는 기타 속성을 나타낸다. 이것은 형식화되지 않은 함수 호출이며 일반적으로 안전하지 않은 반환 형식 때문에 피하는 것이 가장 좋습니다.

function doSomething(f: Function) {
  return f(1, 2, 3);
}

'Front end > Typescript' 카테고리의 다른 글

[Typescript] Utility Types - 2  (0) 2022.03.27
[Typescript] Conditional Types  (0) 2022.03.13
[Typescript] Mapped Types  (0) 2022.02.24
[Typescript] Utility Types - 1  (0) 2022.02.21
[Typescript] Type Guard  (0) 2021.12.18
Comments