Introduction to TypeScript - 3

Written by kwSeo

TypeScript - 3

Function Detail

Function Type

JavaScript는 함수를 일급으로 취급한다. 그래서 함수도 값처럼 얼마든지 인자로 전달하거나 리턴 값으로 받을 수 있다. 그렇기 때문에 리터럴 값처럼 함수도 타입을 지정할 수 있다.

let add: (a: number, b: number) => number = function (a: number, b: number): number { return a + b; };
console.log(add(10, 20));

이전에 변수의 타입을 지정하던 자리에 마치 함수를 선언하듯이 인자와 반환값을 작성하면 된다. =>을 기준으로 왼쪽에 괄호로 감싸져 있는 부분은 함수 인자의 타입을 지정하는 부분이다. 그리고 오른쪽은 함수의 리턴 타입을 의미한다.

Arrow Function

JavaScript의 ES6 스팩에는 Arrow 함수이 추가되었다. 물론 ES6의 super set인 TypeScript도 Arrow 함수를 사용할 수 있다. Arrow 함수은 이름 그대로 화살표(=>)로 간단히 함수를 의미한다.

let add = (a: number, b: number) => a + b;
let not = a => !a;
let addWithLog = (a: number, b: number) => {
    console.log(`a=${a}, b=${b}`);
    return a + b;
}

위 코드처럼 간단하게 함수를 정의할 수 있다(TypeScript는 타입추론을 지원하기 때문에 따로 변수의 타입을 선언하지는 않았다). =>를 기준으로 왼쪽편에 있는 것은 함수의 매개변수이며, 오른쪽은 수행될 코드이다. 만약 수행되는 코드가 한 줄뿐이라면 중괄호({})를 사용할 필요없다. 매개변수도 하나뿐이고, 타입도 작성할 필요가 없다면 괄호(())로 감쌀 필요도 없다. 그런데 간편하게 익명 함수를 작성하는 것 외에도 큰 특징이 존재한다. 바로 Arrow 함수가 정의되는 영역의 this가 자동으로 바인딩된다는 것이다.

class Observable {
    subscriber: Array<(newValue) => void> = [];

    constructor(private _value: number) { }

    subscribe(func: (newValue) => void) {
        this.subscriber.push(func);
    }

    set value(newValue: number) {
        this._value = newValue;
        this.subscriber
            .forEach(func => func(newValue));
    }
}

위와 같은 클래스가 있다고 가정하자. Observable 클래스는 값과 구독자(subscriber) 함수 목록을 가지고 있다. 만일 값이 변경되면 자동으로 모든 구독자 함수를 호출할 것이다.

class ValuePrinter {
    constructor(private obs: Observable, private msg: string) {
        this.obs.subscribe(function(newValue: any) {
            console.log(`value=${newValue} msg=${this.msg}`);
        });
     }   
}

ValuePrinter는 전달받은 가지고 있는 Observable의 값이 변경될 경우 생성할때 받은 메시지와 함께 바뀐 값을 출력한다.

let observable = new Observable(10);
let valuePrinter = new ValuePrinter(observable, "Good print");
observable.value = 20;

위 코드를 수행하면 어떤 결과가 출력될까? 코드를 작성한 개발자가 원하는 출력은

value=20 msg=Good print

일 것이다. 그러나 실제 출력은

value=20 msg=undefined

위와 같다. 이유는 Observable에서 구독자 함수들을 호출할때 this에 바인딩되는 객체가 다르기 때문이다. 일반적인 function 키워드를 사용해서 함수를 정의할 경우, 그 함수는 언제 어디서 호출되느냐에 따라서 this가 가리키는 대상이 달라진다. 위 예제에서는 구독자 함수들을 호출할때 this에는 window 객체(JavaScript의 전역 객체)가 바인딩된다. Arrow 함수를 사용하면 다른 결과가 출력된다.

class ValuePrinter {
    constructor(private obs: Observable, private msg: string) {
        this.obs.subscribe((newValue: any) => {                                   // Arrow function
            console.log(`value=${newValue} msg=${this.msg}`);
        });
     }   
}

출력값:

value=20 msg=Good print

이제야 원하던대로 출력이 되었다. Arrow 함수는 정의되는 순간의 this 객체로 함수내 this가 가리키는 대상이 고정된다. 그렇기 때문에 this는 의도했던대로 ValuePrinter 객체를 가리킨다. 언제 어디서 호출을 하던 this가 가리키는 대상이 고정되있는 것이다. 또한, Arrow 함수는 클래스로서 사용될 수 없다. 즉, Arrow 함수는 new 키워드를 사용해서 객체를 생성할 수 없다는 뜻인데, 기존의 JavaScript에서는 function을 통해서 함수와 클래스를 정의했기 때문에 익명함수로도 객체를 생성할 수 있었다. JavaScript 만의 이상한 문법이다. 함수로 클래스를 정의하고 객체를 생성한다니…… ES6에서 추가된 Arrow 함수를 통해 이러한 혼란을 줄일 수 있다.

Optional and Default Parameters

함수의 매개변수는 필수와 선택으로 나뉜다.

function logError(err: string, msg?: string) {
    if (msg) {
        msg = "no message";
    }
    console.log(`${err}: ${msg});
}

logError("404", "Not Found");   // ok
logError("400") // ok

필수가 아닌 매개변수에는 ?를 붙이면 된다. 필수가 아닌 매개변수에는 기본값도 지정할 수 있다.

function logError(err: string, msg = "no message") {
    if (msg) {
        msg = "no message";
    }
    console.log(`${err}: ${msg});
}

Rest Parameters

함수의 매개변수의 수가 정해져있지 않은 경우 아래와 같은 방법으로 정의할 수 있다.

function logError(err: string, ...msg: string[]) {
    ...
}

Done At: Dec 26,2016
Categories: