Typescript 기초(5) – Functions

T

Typescript Handbook / Developer’s Record 를 참고했습니다.

함수 타입

인자의 타입과 return type을 지정할 수 있다. 또한 익명함수도 만들 수 있어서 변수에 할당도 된다.

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

함수 자체의 타입도 만들 수 있다.

let myAdd : (x: number, y: number) => number;
myAdd = function(x: number, y: number) {
    return x + y
}
console.log(myAdd(3, 5) // 8

인자의 이름이 같을 필요는 없다.

let myAdd : (x: number, y: number) => number;
myAdd = function(a: number, b: number) {
    return a + b
}
console.log(myAdd(3, 5) // 8

타입 추론

let myAdd : (x: number, y: number) => number;
myAdd = function(x, y) {
    return x + y
}
console.log(myAdd(3, 10) // 8

Parameters

Optional

굳이 있어야 하는건 아니다~ 라는 의미로 ?를 쓴다.

function buildName(first: string, last?: string): string {
    if(last) { return first + " " + last }
    else { return first }
}
console.log(buildName("Yoon")) // Yoon
console.log(buildName("Yoon", "Byeongin")) // Yoon Byeongin

Default

기본으로 parameter의 값을 지정할 수 있다.

function 나이(나이: number, 붙임말="살"): string {
    return 나이 + 붙임말 + " 입이다" 
}
console.log(나이(15)) // 15살 입이다 
console.log(나이(62, "세")) // 62세 입이다

만약에 default parameter가 앞에 있어야 한다면, undefined를 넣어 줘야한다.

function buildName(firstName = "Yoon", lastName: string): string {
    return firstName + " " + lastName
}
console.log(buildName(undefined, "Byeongin")) // Yoon Byeongin

Rest

function 친구들이름불러보자(...친구들: string[]): string {
    return 친구들.join(" ")
}
console.log(친구들이름불러보자("CPU", "마우스", "키보드", "모니터")) // CPU 마우스 키보드 모니터

This

this와 화살표 함수

let deck = {
    suits: ["hearts", "spread", "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 }
        }
    }
}
let cardPicker = deck.createCardPicker()
let pickedCard = cardPicker()
console.log("card : " + pickedCard.card + " of " + pickedCard.suit)

[ERR]: Cannot read property 'suits' of undefined

this는 실행하는 환경에 의해 결정된다.

createCardPicker함수를 실행했을때 돌려받는건 함수이다. 그리고 이 함수를 실행할때는 (이 함수가 객체에 들어있는게 아니니까) this는 window가 된다.

하지만, 화살표 함수는 this를 미리 그 this가 지정된 객체로 바인딩 시켜준다.

let deck = {
    suits: ["hearts", "spread", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52)
            let pickedSuit = Math.floor(pickedCard / 13)
            return { suit: this.suits[pickedSuit], card: pickedCard }
        }
    }
}
let cardPicker = deck.createCardPicker()
let pickedCard = cardPicker()
console.log("card : " + pickedCard.card + " of " + pickedCard.suit)
// card : 50 of diamonds

작동은 하지만 this.suits[pickedSuit]thisany라는 단점이 있다.

this parameter

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()
console.log("card : " + pickedCard.card + " of " + pickedCard.suit)
// card : 10 of spades 

createCardPicker의 인자값에 this: Deck을 넣어주니까 this의 데이터 타입을 Deck으로 잡아준다!

callback 함수 안에있는 this

this 쓰지 말아주세요~

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}
class Handler {
    info: string,
    onClickBad(this: Handler, e: Event) {
        this.info = e.message
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad)

이렇게 하면 에러가 뜬다.

Argument of type '(this: Handler, e: Event) => void' is not assignable to parameter of type '(this: void, e: Event) => void'. The 'this' types of each signature are incompatible. Type 'void' is not assignable to type 'Handler'.

this: void 라고 명시해줬는데 this: Handler라고 했으니 당연히 오류가 난거다. 그래서 this: void를 맞춰줘야한다.

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}
class Handler {
    info: string,
    onClickBad(this: void, e: Event) {
        this.info = e.message
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad)

callback함수에서 this써야할때

어떤 라이브러리의 함수에 callback함수로 내 함수를 넘겨줄때가 있다. 그 라이브러리에서 뭔가 처리하고 마지막에 callback함수로 내가 넘겨준 함수를 호출해 주는 경우가 그렇다. 이때 이 callback함수안에 this를 써야할 수도 있는데, 그럼 아래와 같이 화살표 함수를 써주면 된다.

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}
class Handler {
    info: string,
    onClickGood = (e: Event) => {
        this.info = e.message
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood)

하지만, 화살표 함수의 단점은 메모리를 비교적 많이 잡아먹는다. method는 prototype에 붙어서 모든 object가 그 method를 공유하지만, 화살표함수는 클래스를 통해 object를 생성할때마다 계속 함수를 만들어낸다.

Overload

함수의 인자값으로 배열이 들어갈 수도 있고 객체가 들어갈 수도 있고, 아니면 그냥 숫자가 들어갈 수도 있다. 자바스크립트는 dynamic한 언어니까.

let suits = ["hearts", "spades", "clubs", "diamonds"]
function pickCard(x: any): 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: "diamonds", card: 2 },
    { suit: "spades", card: 10 },
    { suit: "hearts", card: 4 }
]
let pickedCard1 = myDeck[pickCard(myDeck)]
console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit)
// card: 2 of diamonds
let pickedCard2 = pickCard(15)
console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit)
// card: 2 of spades

들어가는거에 따라서 다르게 출력되는 이런상황... 타입스크립트에서는 어떻게 대응했을까? 타입스크립트스러운 방식은 다음과 같다.

let suits = ["hearts", "spades", "clubs", "diamonds"]
function pickCard(x: { suit: string, card: number }[]): number
function pickCard(x: number): { suit: string, card: number }
function pickCard(x: any): 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: "diamonds", card: 2 },
    { suit: "spades", card: 10 },
    { suit: "hearts", card: 4 }
]
let pickedCard1 = myDeck[pickCard(myDeck)]
console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit)
// card: 2 of diamonds
let pickedCard2 = pickCard(15)
console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit)
// card: 2 of spades
let pickedCard3 = pickCard("i Love you")

타입스크립트에서는 이런식으로 overload를 한다. 이제 pickCard함수는 2가지 모양을 가지게 된다. 주의할점은, pickCardany타입의 값을 넣으면 안된다. 위에 정한 2가지 타입만 들어갈 수 있다. 마지막에 적은 pickCard("i Love you")는 다음과 같은 에러를 뿜는다.

No overload matches this call. Overload 1 of 2, '(x: { suit: string; card: number; }[]): number', gave the following error. Argument of type '"i Love you"' is not assignable to parameter of type '{ suit: string; card: number; }[]'. Overload 2 of 2, '(x: number): { suit: string; card: number; }', gave the following error. Argument of type '"i Love you"' is not assignable to parameter of type 'number'.

약간 복잡하긴 한데 그래도 쓸모는 있을것같다.

Add Comment