프로그래밍/TypeScript

[TypeScript] this : 타입 지정, 화살표 함수, 콜백 함수

remazitensi 2024. 7. 18. 11:22

this

타입스크립트에서 this는 JavaScript와 마찬가지로 함수가 호출되는 문맥에 따라 다른 값을 가질 수 있습니다. 그러나 TypeScript는 강력한 타입 시스템을 통해 this의 타입을 명시적으로 지정하고, 잘못된 사용을 방지할 수 있습니다.

this 키워드는 JavaScript와 TypeScript에서 매우 중요한 개념으로, 어떻게 작동하는지를 이해하는 것은 필수!!

특히 this가 잘못 사용될 때 발생할 수 있는 문제들을 TypeScript를 통해 찾아낼 수 있습니다.

this와 화살표 함수 (this and Arrow Functions)

화살표 함수는 자신이 정의된 위치에서 this 값을 캡처합니다. 이는 함수가 호출되는 문맥과 관계없이 항상 동일한 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();

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

위 코드에서 createCardPicker 함수가 반환하는 함수 내부의 this는 deck 객체가 아닌 전역 객체를 가리킵니다. 이는 함수가 단순히 호출될 때 발생하는 현상입니다. 이 문제를 해결하려면 ES6의 화살표 함수를 사용하여 this를 올바르게 바인딩할 수 있습니다.

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // 화살표 함수로 `this`를 캡처
        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);

위 예제에서 createCardPicker 메서드는 화살표 함수를 반환하여 this가 항상 deck 객체를 가리키도록 합니다!

 

this 매개변수 (this parameter)

TypeScript에서는 명시적으로 this 매개변수를 지정하여 함수가 호출될 때 this의 타입을 지정할 수 있습니다. 이를 통해 잘못된 this 사용을 방지할 수 있습니다.

this 매개변수는 함수의 매개변수 목록의 가장 앞에 오는 가짜 매개변수입니다!

function f(this: void) { // `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),
    // `this: 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();

console.log("card: " + pickedCard.card + " of " + pickedCard.suit);

createCardPicker 메서드가 Deck 타입의 this를 사용한다고 명시하고 있습니다. 이를 통해 this의 타입을 명확히 지정할 수 있고, 잘못된 호출을 방지할 수 있습니다!

콜백에서의 this 매개변수 (this parameters in callbacks)

콜백 함수에서 this를 사용할 때는 주의가 필요합니다. 콜백 함수는 종종 함수가 호출되는 문맥에서 this가 달라질 수 있기 때문입니다.

콜백 함수의 this는 일반 함수 호출로 인해 undefined가 될 수 있습니다. 이를 방지하려면 this 매개변수를 사용하여 타입을 명시할 수 있습니다.

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

class Handler {
    info: string;
    constructor(info: string) {
        this.info = info;
    }

    onClickGood(this: void, e: Event) {
        console.log('clicked!');
    }

    onClickBad(this: Handler, e: Event) {
        this.info = e.type; // 잘못된 `this` 사용
    }
}

let uiElement: UIElement = {
    addClickListener(onclick) {
        document.addEventListener('click', onclick);
    }
};

let h = new Handler("Handler Info");

uiElement.addClickListener(h.onClickGood); // 올바른 사용
uiElement.addClickListener(h.onClickBad.bind(h)); // `.bind`를 사용하여 `this`를 바인딩

onClickGood 메서드는 this 타입을 void로 지정하여 this를 사용하지 않도록 합니다. onClickBad 메서드는 this를 Handler로 지정하고, .bind를 사용하여 this를 올바르게 바인딩하여 사용합니다.

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

class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // `this`를 사용할 수 있음
        this.info = e.message;
    }
}

let h = new Handler();
uiElement.addClickListener(h.onClickBad); // 오류!

onClickBad 메서드는 Handler 객체의 인스턴스로 호출되어야 합니다. 이를 해결하려면 this 타입을 void로 변경합니다.

class Handler {
    info: string;
    onClickGood(this: void, e: Event) {
        // `this`를 사용할 수 없음
        console.log('clicked!');
    }
}

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

onClickGood 메서드는 this를 사용하지 않으므로 this 타입을 void로 지정할 수 있습니다. 만약 this를 사용하고 싶다면 화살표 함수를 사용할 수 있습니다.

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

화살표 함수는 외부의 this를 캡처하므로, Handler 객체의 info 속성을 안전하게 사용할 수 있습니다. 이 경우, 화살표 함수가 객체마다 하나씩 생성된다는 점을 주의!!

 

Reference

https://typescript-kr.github.io/pages/functions.html

https://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/