티스토리 뷰

 

공부를 시작하며

타입스크립트에서의 함수 표현을 어떻게 하는지에 대해서 알아봅니다.

타입스크립트 문서를 통해서 공부 했습니다.

레퍼런스 : https://www.typescriptlang.org/docs/handbook/2/functions.html

 

Documentation - More on Functions

Learn about how Functions work in TypeScript.

www.typescriptlang.org

 

 

[ 함수 타입 표현식 (Function Type Expressions) ]

함수를 매개변수를 통해서 넘기게 되면 다음과 같이 타입을 지정할 수 있습니다.

function functionTypeExpressions(fn: (v: string) => void) {
  fn("this is function type expressions!!");
}

function sendFunction(v: string) {
  console.log(v);
}

functionTypeExpressions(sendFunction);

함수의 타입 표현식은 (v: string)=>void 와 같은 형식으로 void 가 의미하는 것은 return 문과 같이 반환되는 값이 아무것도 없음을 의미합니다. 여기서 (v)=>void 와 같이 매개변수 값에 아무런 타입을 지정해주지 않으면 자동적으로 any가 지정되게 됩니다.

 

위의 함수 타입 표현식을 다음과 같이 타입 별칭을 이용해서 지정해 줄 수도 있습니다.

type FnType = (v: string) => void;

function functionTypeExpressions(fn: FnType) {
  fn("this is function type expressions!!");
}

 

[ Call Signatures ]

함수에 타입을 지정하여 사용할 수 있습니다. 일반적인 (매개변수:매개변수 타입)=> 반환 타입 이 아닌 : 를 사용해서 지정이 가능합니다.

아래와 같이 콜백 함수와 같이 사용하고 어떤 값이 반환 될 지 유추가 가능합니다.

type PointToRect = {
  (reduce: number): void;
};

function pointReduce(x: number, y: number, reduceFunction: PointToRect) {
  reduceFunction(x + y);
}

pointReduce(10, 20, (reduceNum) => console.log(reduceNum));
// console.log => 30

 

[ Construct Signatures ]

new 생성자를 통해 구조를 만들 때에도 시그니처를 통해 타입을 지정해 줄 수 있습니다.

사용 방법은 new (매개변수:타입): 타입 입니다.

다음 예제에서는 사람의 객체를 만드는 것을 형상화하는 이름을 받아 인스턴스를 생성해 주는 예시입니다.

type IHuman = {
  name: string;
};

type IHumanConstructor = {
  new (name: string): IHuman;
};

class Human implements IHuman {
  age: number;
  constructor(public name: string) {
    this.age = 0;
  }
}

function bornHuman(classes: IHumanConstructor, name: string) {
  // .... do something
  return new classes(name);
}

const newHuman = bornHuman(Human, "asdlkj");
console.log("newHuman: ", newHuman);
// Human { name:'Jone', age:0 }

 

Javascript like Date Object

또한 자바스크립트에서 Date 와 같은 객체를 consturct signatures 형식으로 타입을 지정해줄 수 있습니다.

interface DateObject {
  new (s: string): Date;
}

 

다음과 같이 제네릭 타입을 사용해서 반환되는 값을 추론할 수 있습니다.

변수를 생성하면 타입스크립트는 변수를 할당하는 과정에서 바로 타입을 추론하여 지정합니다. 변수를 함수에 넘기게 되면 지정된 타입을 제네릭 타입과 같이 사용하여 추론하게 됩니다.

function lastArray<T>(arr: T[]): T | undefined {
  return arr[arr.length - 1];
}

const testArr = ["wow", "oh", "test"];

const resultArr = lastArray(testArr);
console.log(resultArr);
// 출력 : test

 

추론

타입스크립트가 제네릭 타입을 통해서 타입을 추론하는 과정은 다음 예시를 보면 이해할 수 있습니다.

따로 customMap 함수 내에서는 지정된 타입없이 오직 제네릭 타입을 통해서 모든 타입을 추론하는 것을 확인할 수 있습니다. 

function customMap<Input, Output>(
  array: Input[],
  fn: (arg: Input) => Output
): Output[] {
  return array.map(fn);
}

const stringNumArr = ["0", "1", "2", "3"];

const stringNumArrParseToNumArr = customMap(stringNumArr, (item) =>
  parseInt(item)
);
console.log(stringNumArrParseToNumArr);
// 출력 : [0, 1, 2, 3]

 

인수 타입 지정

다음과 같이 두 배열을 합쳐주는 함수가 있습니다. 하지만 아래의 함수는 호출과정에서 다음과 같이 지정하면 오류가 납니다. 첫번째 인자로 넘긴 number[] 타입이 제네릭 타입의 기준이 되어 string 을 number 타입에 지정할 수 없다고 표시됩니다.

 

Before

function combineArr<T>(arrA: T[], arrB: T[]): T[] {
  return arrA.concat(arrB);
}

combineArr([1, 2, 3], ["string"]);
// Type 'string' is not assignable to type 'number'.

After

이때엔 다음과 같이 유니언 타입으로 지정해주면 오류를 피할 수 있습니다.

combineArr<number | string>([1, 2, 3], ["string"]);

 

 

[ 선택적 매개변수 (Optional Parameters) ]

매개변수를 항상 받고 싶지 않을 때도 있을겁니다. 그럴때 매개변수의 타입 : 의 전에 ? 를 붙이면 선택적으로 받을 수 있습니다.

// ? 를 붙임으로써 선택적으로 매개변수를 받을 수 있습니다.
function func(v?: string) {
  // .....
  if (v) {
    v.toLocaleUpperCase();
  } else {
    // ...
  }
}

func("string value");
func();

 

기본값을 제공하기

파라미터에 기본값을 제공하면 타입이 자동으로 추론됩니다.

// 기본 파라미터값을 제공해 줄 수도 있습니다.
function func(v = "default") {
  // ....
}

func("test");
func();

 

선택적 매개변수의 undefined 허용

다음과 같이 선택적 매개변수는 undefined 를 인자로 넘겨도 오류를 표시해주지 않습니다.

? 를 통해 선택적 매개변수 타입을 선언하면 타입 | undefined 와 같이 선언되기 때문입니다.

대신 null 값은 조금 다른데요, 다음과 같이 tsconfig.json을 통해서 strictNullChecks 옵션의 여부에 따라 null 값을 허용해주거나 허용 안해줍니다.

declare function func(v?: string): void;

func();
func("default");
func(undefined);

// strictNullChecks : false
func(null);

// strictNullChecks : true
func(null);
// Argument of type 'null' is not assignable to parameter of type 'string | undefined'.

 

실수하기 쉬운 콜백의 선택적 매개변수

다음과 같이 함수로 각 배열의 아이템과 인덱스를 콜백의 매개변수 값으로 받을 수 있게 작성하였습니다.

function forEach(
  array: any[],
  callback: (item: any, index?: number) => void
) {
  for (let i = 0; i < array.length; i++) {
    callback(array[i], i);
  }
}

forEach([1, 2, 3], (item) => console.log(item));
forEach([1, 2, 3], (item, index) => console.log(item, index));

하지만 위와 같이 작성하게 되면 다음과 같이 선택적으로 받는 매개변수인 index 값은 undefined도 반환할 수 있다 판단하여 매서드를 사용하거나 해당 index 값을 사용하려 하면 오류가 표시됩니다.

forEach([1, 2, 3], (item, index) => index.toFixed());
// Object is possibly 'undefined'.

 

위의 forEach 함수의 구현부에서는 다음과 같이 index 매개변수가 없을 수도 있다고 판단하기 때문입니다.

for (let i = 0; i < array.length; i++) {
  // index 매개 변수가 인자값으로 없을 수도 있습니다.
  callback(array[i]);
}

 

* 따라서 타입스크립트에서는 콜백함수를 작성하게되면 매개변수에는 선택적 매개변수는 지양하라고 설명합니다.

 

 

 

[ 오버로드 서명 및 구현 서명 ( overload signatures ) ]

다음과 같이 함수의 타입을 다중으로 오버로드로 작성할 수 있습니다.

이때 함수는 내부의 서명은 2개이상 오버로드 된다면 볼 수 없으며 +?? overload 와 같이 표시만 됩니다.

function getLength(v: string): number;
function getLength(v: number[]): number;

function getLength(v: any) {
  return v.length;
}

getLength("ddd");
// 다음과 같이 함수 구현에 사용되는 서명은 외부에서 볼 수 없습니다.
// function getLength(v: string): number (+1 overload)
getLength([0, 1, 2, 3]);

 

또한 구현부 함수에서의 서명은 오버로드에서의 서명과 일치해야 합니다.

다음과 같이 구현부에서의 서명과 오버로드의 서명이 일치하지 않았을때에 두 서명이 일치하지 않는다는 오류가 표시되게 됩니다.

function func(v: string): void;
// This overload signature is not compatible with its implementation signature.
function func(v: boolean): void;

function func(v: number) {}

 

* 유니언 타입을 쓰자!

이러한 구현하기 힘든 타입의 특성 때문에 타입스크립트 문서에서는 유니언(Union) 타입을 사용하라고 권장하고 있습니다.

function func(v: string | number) {
  // ....
}

 

[ 다른 유형들 ]

 

void

함수에서 return 문에 아무 값도 반환하지 않았을땐 undefined 가 아닌 void 로 분류됩니다.

function nothing() {
  return;
}
// void

function thisIsSameNothingFunction(): void {
  return;
}

 

unknown

any 타입과 unknown 타입이 있습니다. any 는 모든 타입을 다 허용하여 매개변수에 매서드가 있는지 없는지 알 수 없어도 타입 오류가 발생하지 않고 unknown 는 타입 오류가 발생합니다. 따라서 any 보다는 unknown 이 더 안전합니다.

function funcAny(v: any) {
  v.draw();
}

function funcUnknown(v: unknown) {
  v.draw();
  // Object is of type 'unknown'
}

 

never

함수에서 값을 반환하지 않고 throw 를 통해서 프로그램 실행을 종료할 때 never의 타입을 지정합니다.

function customError(message: string): never {
  // ....
  throw new Error(message);
}

 

유니언 타입에서 아무 값도 반환되지 못할 때 never 타입이 나타납니다.

function neverUnions(v: string | number) {
  if (typeof v === "string") {
    //...
    console.log(`${v} is string`);
  } else if (typeof v === "number") {
    console.log(`${v} is number`);
  } else {
    //....
    v;
    // (parameter) v: never
  }
}

 

Function

Function 타입을 사용하면 자바스크립트에 bind, call, apply 등 프로퍼티가 있는 함수 타입을 지정해 줄 수 있습니다.

하지만 반환되는 타입이 무엇인지 알 수 없는 any로 지정이 되기 때문에 ()=> void 와 같이 함수를 명시적으로 타입을 지정해주는 것이 더 좋습니다.

// Before
function functionType(fnc: Function) {
  return fnc(1, 2, 3);
}
// function functionType(fnc: Function): any


// After
function voidFunctionType(fnc: (n: number) => void) {
  return fnc(1);
}
// function voidFunctionType(fnc: (n: number) => void): void

 

나머지 매개변수와 인수 (  Rest Parameters and Arguments )

다음과 같이 나머지 매개변수에 대한 타입을 선언할 수 있습니다. 나머지 변수는 배열 타입으로 받게되어 배열 메서드를 사용할 수 있습니다.

 

function increaseArrayNum(increaseNum: number, ...array: number[]) {
  console.log(array);
  // [1, 2, 3, 4, 5]
  return array.map((v) => increaseNum + v);
}

const result = increaseArrayNum(5, 1, 2, 3, 4, 5);
console.log(result);
// [6, 7, 8, 9, 10]

 

나머지 인수

자바스크립트에서 ... 스프레드 구문을 사용하면 배열 또는 객체 내부의 값들을 펼쳐서 제공할 수 있습니다.

다음과 같이 메서드에서 두 인자 값을 받는 상황에서 스프레드 구문을 사용하면 튜플 타입을 사용해야한다고 오류가 발생합니다.

const atn2 = [1, 2];

const angle = Math.atan2(...atn2);
// A spread argument must either have a tuple type or be passed to a rest parameter

 

여러가지 방법이 있겠지만 다음과 같은 방법으로 해결이 가능합니다.

 

튜플 타입 tuple type

const atn2: [number, number] = [1, 2];

const angle = Math.atan2(...atn2);

상수 단언 as const

일반적으로 const 를 사용해서 단언하는 것이 간단한 해결책입니다.

const atn2 = [1, 2] as const;

const angle = Math.atan2(...atn2);

 

매개변수의 분해 ( Parameter Destructuring )

매개변수에 객체를 전달할 때 다음과 같이 구조를 분해해서 타입을 지정해줍니다.

function printUser({
  name,
  age,
  country,
}: {
  name: string;
  age: number;
  country: string;
}) {
  console.log(
    `my name is ${name}. I'm ${age} years old and I live in ${country}.`
  );
}

printUser({ name: "Jone", age: 20, country: "South Korea" });

 

타입 별칭을 통해서 타입을 지정해 줄 수도 있습니다.

type User = {
  name: string;
  age: number;
  country: string;
};

function printUser({ name, age, country }: User) {
  console.log(
    `my name is ${name}. I'm ${age} years old and I live in ${country}.`
  );
}
댓글
최근에 올라온 글