TypeScript functions

Functions

목차

  1. Introduction

  2. Functions

  3. Function Types

  4. Optional and Default Parameters

  5. Rest Parameters

  6. Overloads

Introduction

함수는 어떤 JS 어플리케이션에서든 근본이 되는 요소입니다. 함수는 추상화 레이어를 구축하고 클래스를 모방하고, 정보를 숨기고, 모듈을 만들 수 있게 해줍니다. 타입스크립트에선, 비록 클래스와 네임스페이스와 모듈이 있기는 하지만, 함수는 여전히 무언가를 하는것을 구현하는데 핵심적인 역할을 합니다. 타입스크립트는 또한 새로운 기능들을 js 함수에 추가하여 더욱 작업하기 쉽게 해줍니다.

Functions

JS처럼 타입스크립트의 함수도 익명과 그렇지 않는 함수로 만들어 질 수 있습니다. API에서 함수 리스트를 만들거나 혹은 다른 함수에 함수를 전달하던 당신은 가장 적합한 방식을 당신의 어플리케이션에서 선택할 수 있습니다. 빠르게 두 접근법을 요약해 보자면,

//Named Function
function add(x, y) {
    return x + y
}
//Anonymous function
let myAdd = functino (x, y) { return x + y };

자바스크립트와 같이 함수는 함수 바디 밖에 있는 변수들을 참조할 수 있습니다. 이렇게 할때, 이 변수들을 붙잡았다 말할 수 있습니다.

let z = 100;

function addToZ(x, y) {
    return x + y + z;
}

Function Types

Typing the function

이전 간단한 예제에 타입을 넣어봅시다.

function add(x: number, y: number): number {
    return x + y;
}

let myAdd = function(x: number, y: number): number {return x + y};

우리는 인자값과 반환값에 타입을 추가할 수 있습니다. TypeScript는 return 문을보고 리턴 유형을 파악할 수 있으므로 많은 경우에 선택적으로 타입을 남겨놓습니다.

Writing the function type

함수 타입을 선언하는 법

function addFunc(x: number, y: number) { return x + y };

let add: (x: number, y: number) => number = addFunc;

위와 같이 인자값들과 반환값으로 함수의 타입을 선언해 줄 수있다. add 변수에는 이제 인자값으로 2개의 숫자를 받을 수 있고 반환값으로 숫자를 반환하는 함수만을 대입연산자로 받아들일 수 있다.
함수 타입을 선언할 때, 반환값의 타입을 정하는 것은 의무이다. 아무것도 반환하지 않을 경우에는 void를 선언해주어야만 한다.
함수 타입은 그저 타입일 뿐 클로저를 제공하지는 않는다.

Inferring the types

let myAdd = function(x: number, y: number): number { return x + y};

let myAdd: (x; number, y: number) => number = function(x, y){return x + y};

첫번째 myAdd의 경우에는 각각의 변수와 반환값에 모두 타입을 지정해 주어야했다. 하지만 두번째 경우에는 변수에 함수 타입을 지정해 줌으로써, 할당하는 함수에 굳이 타입을 지정하지 않더라도 자동으로 변수와 반환값을 판단해 준다.

Optional and Default Prameters

함수의 인자값을 선택으로 남겨놓고 싶을때 ?를 사용하여 남겨 놓거나, 기본값을 지정함으로써 선택으로 남겨놓을 수 있다. 예를 들자면

function buildName(first: string, last?: string) {
    if(last) {
        return first + ' ' + last;
    } else return first;
}

function buildName(first: string, last='doe') { return first + ' ' + last};

첫번째 경우는 ?를 사용하여 선택으로 남겨두었고, 두번째 경우는 기본값을 doe로 설정함으로써, 굳이 인자값으로 넘기지 않아도 자동으로 doe가 출력됨을 알 수있다.

한가지 추가적으로 알아야 할 사항은, 2번재 경우에 last = 'doe'에서 타입을 선언을 하지 않았지만, 기본값을 설정할때, 해당 기본값의 타입으로 자동으로 타입선언이 된다.

보통 필수인 값을 앞으로 오게 하고 선택 변수들을 뒤에 놓게 한다. 반대의 경우도 가능은 하지만 인자값으로 넘긴 값이 첫번째 인자값인지 2번재 인자값인지 판단할 수 없기 때문에

undefined를 써주어 그 인자값이 선택사항이라는 것을 명시해 주어야 한다.

예를 들자면

function buildName(first?: string, last: string) {
    if (first) {
        return first + ' ' + last;
    } else return last;
}

buildName(undefined, 'smith') //smith

기본값을 설정해 준다 하더라도 undefined를 넘겨주어야 한다.

function buildName(first='jim', last: string) {
    return first + ' ' + last;
}

buildName(undefined, 'smith') //'jim smith'

Rest Parameters

여러개의 인자값을 정해지지 않은 길이로 인자값을 추가하고 싶을때, 보통 js에서는 arguments객체를 통해 접근 할 수 있었다.

function add () {
    return arguments[0] + arguments[1];
}

add(1, 2) // 3

TypeScript도 동일한 작동을 수행할 수 있지만 약간 다른 방식으로 접근 가능하다. 그 방법은 인자값에 ...을 추가하고 나머지 인자값들의 타입을 지정해 주는 방식이다.

예를 들자면

function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + ' '+ restOfName.join(' ');
}

let names = buildName('hello', 'world', 'this', 'is', 'shin'); // firstName은 반드시 명시해 주어야 한다. 배열로 ...array는 에러가 난다.

또한 함수타입의 인자 타입으로도 나머지 인자값들을 지정해 줄 수 있다.

function buildName(firstName: string, ...restOfName: string[]): string {
    return firstName + ' '+ restOfName.join(' ');
}

let nameFunc: (firstname: string, ...restOfName: string[]) => string = buildName

this

let deck = {
    suits: ['hearts', 'spades', 'clubs', 'diamonds'],
    cards: Array(52),
    createCardPicker: function() {
        return function() {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {
                suit: this.suits[pickedSuit], card: pickedCard % 13
            }
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

js에서는 this가 무엇인지 매우 중요한 반면에 this는 코드를 어지럽히는 주요 요인이기도 하다. 특히 함수에서의 this는 그 함수가 어디에 속하냐에 따라 this의 의미가 달라지기 때문에 어떤 문맥에서 this가 사용되었는지 파악하는 것이 매우 어려웠다. 위의 코드의 경우 에러를발생시키는데 그 이유는 createCardPicker: function() ...로 묶어 버리는 바람에 this의 의미가 global이 되어서 global에는 suits가 없기 때문에 error를 발생시킨다. 위의 createCardPicker:function()...대신에 `

하지만 타입스크립트에서는 이러한 혼란의 발생을 줄이기 위해 this가 무엇인지 명시 할 수있게 하였다.

interface Card {
    suit: string;
    card: number;
}

interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}

let deck: Deck = {
    suits: ['hearts', 'spades', 'clubs', 'diamonds'],
    cards: Array(52),
    createCardPicker: function(this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {
                suit: this.suits[pickedSuit], card: pickedCard % 13
            };
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

this를 명시적으로 표현하는 방법은 인자값으로 this: Deck과 같이 명시해 주는 것이다. 이렇게 함으로써 this 객체의 형태는 반드시 Deck과 같은 형태가 되어야만 한다.

this parameters in callbacks

You can also run into errors with this in callbacks, when you pass functions to a library that will later call them.
콜백 함수를 인자값으로 넘길때 에러가 발생할 수 있다.

Because the library that calls your callback will call it like a normal function, this will be undefined.
왜냐하면 콜백을 불러올 라이브러리가 일반 함수처럼 콜백을 호출할 것임으로 thisundefined일 것이다.

With some work you can use this parameters to prevent errors with callbacks too.
콜백에서 에러가 터지는 것을 막기위해 위의 this인자값을 사용했던 것과 마찬가지로 this인자값을 사용할 수 있습니다.

First, the library author needs to annotate the callback type with this:
먼저 라이브러리 제작자는 콜벡에 this의 타입을 명시해 주어야 합니다.

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}

this: void means that addClickListenerexpects onclick to be function that does not require a this type. Second, annotate your calling code with this:
this: void는 의미합니다. addClickListeneronclick 콜백이 this의 타입을 요구하지 않는것을 기대합니다. 두번째로, 명시하싶시오 당신의 호출하는 코드를 this와 함께.
this: voidaddClickListeneronclick콜백이 this의 타입을 요구하지 않는다는 것을 의미합니다. 두번째로 콜백에 this를 사용하여 넘겨보세요.

class Handler {
    info: string='hello world';
    onclickBad(this: Hanlder, e: Event) {
        this.info = e.message;
    }
}

let h = new Handler();
uiElement.addClickListener(h.onclickBad);

With this annotated, you make it explicit that onClickBad must be called on an instance of Handler.
명시된 this와 함께, onclickBad는 반드시 Handler 인스턴스에서 호출되어야 한다는 것을 명시적으로 만듭니다.

Then TypeScript will detect that addClickListener requires a function that has this: void.
타입스크립트는 thisvoid를 인터페이스에서 설정해 놨는데 Hanlder의 onclickBadthis를 Hanlder로 설정했다는 것을 찾아 낼 것입니다.

To fix the error, change the type of this:
에러 뜨는 것을 고치기 위해 this의 타입을 바꿔야 합니다.

class Handler {
    info: string;
    onclickGood(this: void, e: Event) {
        console.log('clicked');
    }
}
let h = new Handler();
uiElement.addClickListener(h.onclickGood);

Because onclickGood specifies its thistype as void, it is legal to pass to addClickListener.
this타입을 void로 정의했기 때문에 더이상 에러를 발생시키지 않습니다.

Of course, this also means that it can’t use this.info.
물론, 이건 this.info를 사용할 수 없다는 것을 의미합니다.

If you want both then you’ll have to use an arrow function
콜백에 this를 쓰고 this가 Handler를 가르키고 싶다면 arror 함수를 써야합니다.

class Handler {
    info: string;
    onclickGood = (e: Event) => {
        this.info = e.message;
    }
}

arrow 함수가 this를 잡아두지 않기 때문에, 비록 this: void를 콜백으로 요구하더라도 통과할 수 있습니다.
단점은 Handler 객체가 하나 생성될때만다 arror 함수가 생성된다는 점입니다.
반면에 method들은 오직 한번만 만들어지고 Handler의 프로토타입에 붙는다는 점입니다. 그들은 모든 Handler 객체들 사이에서 공유됩니다.

Overloads

여러형태의 반환값을 가지는 것은 js에서 흔하지 않지만 있을 수 있는 일이다. 이를 해결하기 위해 타입을 overriding 할 수 있다.

아래의 코드를 보면

let suits = ["hearts", 'spades', 'clubs', 'diamonds'];

/* override */
function pickCard(x: {suit: string; card: number}[]): number;
function pickCard(x: number): { suit: string; card: number}; 
function pickCard(x): any {
    if(typeof x === 'object') {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    } else if( typeof x == 'number') {
        let pickedSuit = Math.floor(x / 13);
        return {
            suit: suits[pickedSuit], card: x % 13
        }
    }
}

let myDeck = [
    {suit: 'diamond', card: 2},
    {suit: 'spades', card: 10},
    {suit: 'hearts', card: 4},
]

let pickedCard1 = myDeck[pickCard(myDeck)];
console.log('card: ' + pickedCard1.card + ' of ' + pickedCard1.suit);

let pickedCard2 = pickCard(15);
console.log('card: ' + pickedCard2.card + ' of ' + pickedCard2.suit);

오버라이딩은 다음과 같이 함수를 다른 타입으로 선언함을써 사용 가능하다. 작동 방식은 함수가 사용되면 인자값으로 맞는 타입인지 확인 하고 아닐경우 다음 선언된 함수 타입으로 넘어간다. 위의 경우에서는 { suit: string; card: number}인 배열을 인자값으로 받았는지 확인하고, 만약 일치하지 않을경우에는 number인지 확인한다.

만약에 2가지 오버라이딩 된 타입들 이외의 값을 인자값으로 넘겼을 경우에는 error를 발생시킨다.(비록 pickCard(x): any 가 밑에 있더라고 하더라도 무시된다.)

1 Like