Typescript 함수

오호·2022년 1월 27일
0
post-thumbnail
post-custom-banner

이전까지 타입스크립트에서 사용되는 다양한 타입들에 대해서 알아보았다. 이제는 함수를 어떻게 사용하는지 알아보자.

1. 함수 선언과 호출

자바스크립트에서 함수는 일급 객체다. 즉 객체를 다루듯이 함수를 변수에 할당하거나, 함수를 다른 함수로 전달하거나 함수에서 함수를 반환하거나, 객체와 프로토타입에 할당하거나 하는 작업들을 할 수 있다.

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

보통 함수 매개변수의 타입은 명시적으로 정의한다.

타입스크립트는 대부분의 상황에서 타입을 추론하지만 매개변수에는 타입을 추론하지 않는다. 반환타입은 원하면 명시해줄 수 있다.

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

타입스크립트에서 함수를 선언하는 방법은 다섯가지이다.

// 1.
function greet(name: string) {
	return 'hello' + name;
}

// 2. 
let greet2 = function (name: string) {
  	return 'hello' + name; 
}

// 3.
let greet3 = (name : string) => {
	return 'hello' + name;
}

// 4.
let greet4 = (name : string) => 'hello' + name;

// 5.
let greet5 = new Function('name', 'return "hello " +, name');

1.1 선택적 매개변수와 기본 매개변수

함수에서는 ?연산자를 이용해서 선택적 매개변수를 지정할 수 있고 기본 매개변수를 지정할 수도 있다.

function log(message: string, userId?:string) {
  let time = new Date().toLocaleTimeString();
}

function log2(message: string, userId = 'not signed in') {
  let time = new Date().toLocaleTimeString();
}

1.2 나머지 매개변수

타입스크립트에서도 arguments 객체를 통해 나머지 매개변수를 받을 수 있지만 ES6와 혼용할 때 안전하지 않다. 그러므로 rest 파라미터를 쓰는 것이 안전하다.

function sumVariadicSafe(...numbers :number[]):number {
  return numbers.reduce((total, n) => total + n, 0); 
}

sumVariadicSafe(1,2,3); // 6

1.3 호출 시그니처

함수의 전체 타입을 표현하는 방법을 알아보자.

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

sum은 두 개의 number를 인수로 받아 한 개의 number를 반환하는 함수이다.

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

이 코드는 호출 시그니처 또는 타입 시그니처라고 부른다. 함수 호출 시그니처는 타입 수준 코드, 즉 값이 아닌 타입 정보만 포함한다.

실제로 사용할 땐 다음과 같이 사용한다.

type Log = (message : string, userId?: string) => void;

let log: Log = ( message, userId='Not signed in') => {
  let time = new Date().toISOString();
  console.log(time, message, userId);
}

1.4 오버로드된 함수 타입

// 단축형 호출 시그니처
type Log = (message : string, userId?: string) => void;

// 전체 호출 시그니처
type Log = {
  (message : string, userId?:string) : void;
}

Log 처럼 간단한 상황이라면 단축형을 주로 활용하고 복잡한 상황이면 전체 호출 시그니처를 사용하면 되겠다. 오버로딩이 좋은 예시이다.

reserve 함수를 구현해보자.

type Reserve = {
  (from: Date, to: Date, destination: string): Reservation; 
}
let reserve : Reserve = (from, to, destination) => {
  // ...
}

발리로 여행가려는 고객이 있다면 from 과 to에는 날짜, destination은 'bali'로 설정해 사용할 것이다.

다음처럼 편도 여행을 위한 api를 만들 수도 있다.

type Reserve = {
  (from: Date, to: Date, destination: string): Reservation;
  (from: Date, destination: string): Reservation;
}

실행하려고 하면 이런 에러가 발생하는 것을 확인할 수 있다.

type을 정의할 때는 유니온으로 평가되지만 구현할 때는 직접 합쳐서 구현해야 한다.

type Reserve = {
  (from: Date, to: Date, destination: string): Reservation;
  (from: Date, destination: string): Reservation;
}

let reserve: Reserve = ( 
  from: Date, toOrDestination: Date | string,   destination?: string) => 
{
  // ...
}

2. 다형성

filter함수를 구현하면서 알아보자.

function filter(array, f) {
  let result = [];
  for (let i =0; i< array.length; i++) {
    let item = array[i];
    if (f(item)) {
      	result.push(item);
    }
  }
  return result;
}

filter([1,2,3,4], _ => _ <3); // [1,2];
// 1단계
type Filter = {
  (array: unknown, f:unknown) => unknown[];
}

// 2단계
type Filter = {
  (array: number[], f: (item: number) => boolean): number[]; 
}

number[] 로 바꾸는 건 어렵지 않지만 number배열 이외에도 여러 타입의 배열들을 가질 수 있도록 오버로드를 이용해보자.

// 3단계
type Filter = {
  (array: number[], f: (item: number) => boolean): number[];
  (array: string[], f: (item: number) => boolean): string[];
}

객체 배열을 전달하고 싶다면 object[]를 추가하면 될 것 처럼 보이지만 쉽게 해결되지는 않는다. object라는 타입은 앞에서도 살펴보았듯이 존재한다는 사실말고는 아무것도 제공하지 않기 때문이다.

이럴 때는 제네릭을 쓰면 문제를 해결할 수 있다

type Filter = {
  <T>(array: T[], f: (item: T) => boolean): T[] 
}

"filter함수는 T라는 제네릭 타입 매개변수를 사용한다. 이 타입은 무엇인지 지금은 알 수 없으니 누군가 filter를 호출할 때마다 타입스크립트가 타입을 추론해주길 바란다" 라는 뜻이 담겨져 있다고 할 수 있겠다

꺽쇠 괄호로 제네릭 타입 매개변수임을 선언한다. 꺽쇠 기호를 추가하는 위치는 한정되어 있다. 아무데나 할 수는 없다..

함수의 매개변수가 함수를 호출할 때 건네진 인수로 매번 다시 한정되듯 T도 filter를 호출할 때마다 새로운 타입으로 한정된다.

type Filter = {
  <T>(array: T[], f: (item: T) => boolean) : T[]
}
let filter: Filter = (array, f) => // ...

// T number로 한정됨
filter([1,2,3], _ => _ > 2);

// T string으로 한정됨
filter(['a','b']), _ => _ !== 'b');

첫 번째 제네릭 추론 과정을 글로 설명해보자.

  1. filter의 타입 시그니처를 통해 array가 타입이 T인 요소들로 이루어진 배열임을 알게 된다.

  2. 전달된 인수 array[1,2,3] 을 통해 T가 number라는 사실을 알게된다.

  3. 이제 모든 T를 number 타입으로 대치한다. 따라서 매개변수 f: (item:T) => booleanf: (item : number) => boolean 이 되고 반환 타입 T[]는 number[]가 된다.

📍 제네릭은 함수의 기능을 일반화하여 설명할 수 있는 도구이다. 제네릭을 사용하면 재사용성을 높이고 간결하게 유지하는 데 도움을 준다.

2.1 제네릭 타입 별칭

click 이나 mousedown 같은 DOM 이벤트를 설명하는 MyEvent 타입을 정의해보자.

type MyEvent<T> = {
  target : T
  type : string
}

타입 별칭에서는 타입 별칭명과 = 기호 사이에만 제네릭을 선언할 수 있다.

type ButtonEvent = MyEvent<HTMLButtonElement>

2.2 제네릭 타입 기본값

제네릭 타입에도 기본값을 설정해줄 수 있다.

type MyEvent<T> = {
  target : T
  type : string
}

특정요소 타입을 알 수 없는 떄를 대비해 기본값을 추가해보자.

type MyEvent<T = HTMLElement> = {
  target : T
  type: string
}
profile
오호
post-custom-banner

0개의 댓글