typescript - this and arrow functions

2020. 3. 6. 18:29typescript/typescript-grammar

this

this는 함수를 호출할 때 생성된다. this는 파워풀하고 편리하지만 함수를 실행시키는데 비용이 발생한다. 또한 this는 특히 return을 해줄때나 파라미터로 넘겨줄 때 매우 혼란스럽고 악명높다.

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();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

 

해당 코드를 작성해보면 당연히 alert가 발생할 것 같지만 error가 발생합니다. 왜냐하면 return의 this는 deck이 아닌 window이기 때문입니다. 왜냐하면 자기 자신을 호출하기 때문입니다. 최상위에서 함수를 호출하면 일반적으로 this는 window가 됩니다.(strict mode에서는 window 대신 undefined입니다.)  

 

우리는 나중에 함수가 return을 하기전에 this를 올바른 값으로 바운딩함으로써 고칠 수 있습니다. 이 방법을 사용하기 위해서는 ECMA SCRIPT6에 나오는 Arrow Function으로 함수 표기법을 바꿔야합니다. Arrow Function은 함수를 호출하는 곳이 아닌 생성된 곳의 this를 캡쳐합니다. 

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
        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();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

 

this Parameter

this.suits[pickedSuit]는 여전히 any 타입입니다. 왜냐하면 this 함수 표현식 내부의 객체 리터럴이기 때문입니다. 이러한 점을 고치기 위해 

this파라미터를 제공해주어야 합니다. 함수 파라미터 리스트에 처음오는 속임수 파라미터입니다. 아래와 같이 사용하는 겁니다.

function f(this: void) {
    // make sure `this` is unusable in this standalone function
}

 

아래와 같이 파라미터에 대놓고 this를 Deck로 지정해주어서 Any type이 안되는 것입니다. 따라서 this는 Deck 타입입니다.

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),
    // NOTE: The function now explicitly specifies that its callee must be of type Deck
    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();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

 

this partmeter in callbacks

callback 함수에서 라이브러리에 함수를 넘겨줄 때 에러가 발생하곤 한다. callback함수를 일반 함수들 처럼 부르다보면 this가 undefined로 설정되기 때문이다. 

 

우선 콜백 타입에 this를 사용하여 주석을 다는 것이 중요하다.

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

 

this를 사용하여 호출코드와 함께 annotate를 달아야합니다. 

class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // oops, used `this` here. using this callback would crash at runtime
        this.info = e.message;
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!

 

onclick bad는 핸들러의 인스턴스에서 호출되어야 한다. 그래서 에러를 고치기 위해서는 this의 타입을 void로 고쳐주어야한다. 

class Handler {
    info: string;
    onClickGood(this: void, e: Event) {
        // can't use `this` here because it's of type void!
        console.log('clicked!');
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);

 

this의 타입이 void로 오류가 발생하진 않지만 this.info를 사용할 수 없습니다. 만약 오류도 없고 this.info도 사용하고 싶다면 arrow function을 사용해야합니다. 

 

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

이코드에서 this는 method 외부의 this를 사용합니다. 그래서 항상 this:void를 넘겨주어야 한다. 

 

단점으로는 핸들러의 만들어지는 객체는 arrow function을 사용해야합니다. 반면 Method는 한번 만들어지고 prototype에 부착됩니다. Method들은 type Handler의 모든 객체 사이에 공유됩니다. 

 

 

 

 

 

 

'typescript > typescript-grammar' 카테고리의 다른 글

[typescript] Promise와 Async/Await 구문  (0) 2021.03.11
6. typescript - class  (0) 2020.03.06
5. typescript - interfaces  (0) 2020.03.03
4. typescript - 변수 선언  (0) 2020.02.29
3. typescript - 타입  (0) 2020.02.27