Typescript 기초(5) – Functions

Function에 대해 알아봅니다

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)

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'.

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

Leave a Reply

Your email address will not be published.