아이템 49 콜백에서 this 에 대한 타입 제공하기
class C {
vals = [1, 2, 3];
logSquares() {
for (const val of this.vals) {
console.log(val * val);
}
}
}
const c = new C();
c.logSquares();
const c = new C();
const method = c.logSquares;
method();
// Cannot read properties of undefined (reading 'vals')
c.logSquares()가 실제로는 두 가지 작업을 수행해서 문제가 발생한다.C.prototype.logSquares를 호출하고this의 값을c로 바인딩한다.
logSquares의 참조 변수를 사용함으로써 두 가지 작업을 분리했고this의 값은undefined로 설정된다.
const c = new C();
const method = c.logSquares;
method.call(c);
call을 사용하면 명시적으로this를 바인딩하여 문제를 해결할 수 있다.
클래스 내에서 onClick 핸들러를 정의하면 다음과 같다.
declare function makeButton(props: { text: string; onClick: () => void }): void;
class ResetButton {
render() {
return makeButton({ text: 'Reset', onClick: this.onClick });
}
onClick() {
alert(`Reset ${this}`);
}
}
ResetButton에서onClick을 호출하면this바인딩 문제로 인해 “Reset undefined” 라는 경고를 표시한다.
생성자에서 메서드에 this 를 바인딩해서 해결할 수 있다.
class ResetButton {
constructor() {
this.onClick = this.onClick.bind(this);
}
render() {
return makeButton({ text: 'Reset', onClick: this.onClick });
}
onClick() {
alert(`Reset ${this}`);
}
}
- 생성자에서
this.onClick으로 바인딩하면onClick속성에this가 바인딩되어 해당 인스턴스에 생성된다. - 속성 탐색 순서에서
onClick인스턴스 속성은onClick프로토타입 속성보다 앞에 놓이므로render()메서드의this.onClick은 바인딩된 함수를 참조하게 된다.
더 간단한 방법으로도 해결할 수 있다.
class ResetButton {
render() {
return makeButton({ text: 'Reset', onClick: this.onClick });
}
onClick = () => {
alert(`Reset ${this}`);
};
}
onClick을 화살표 함수로 변경하면ResetButton이 생성될 때마다 제대로 바인딩된this를 가지는 새 함수를 생성하게 된다.
콜백을 화살표 함수로 작성하고 this 를 참조하면 타입 오류가 발생한다.
declare function makeButton(props: { text: string; onClick: () => void }): void;
function addKeyListener(el: HTMLElement, fn: (this: HTMLElement, e: KeyboardEvent) => void) {
el.addEventListener('keydown', e => {
fn(e);
// 'void' 형식의 'this' 컨텍스트를 메서드의 'HTMLElement' 형식 'this'에 할당할 수 없습니다.
});
}
class Foo {
registerHandler(el: HTMLElement) {
addKeyListener(el, e => {
this.innerHTML;
// 'Foo' 형식에 'innerHTML' 속성이 없습니다.
});
}
}
요약
this바인딩이 동작하는 원리를 이해하자.- 콜백 함수에서
this를 사용해야 한다면 타입 정보를 명시하자.