이펙티브 타입스크립트 (댄 밴더캄 지음) 를 읽고 정리
📍요약
✅콜백에서 this를 사용해야 한다면, this 바인딩을 확인해야 한다. 확인하려면 2가지 방법이 있다.
- 생성자에서..
- 화살표 함수를 사용..
📍JavaScript this 복습
C class를 만들고
logSquares 메서드를 정의한다
C의 인스턴스 c를 정의하고 메서드를 실행 => 정상
(this = c)
c의 메서드를 새로운 변수에 저장하고 그것을 호출 => 에러
(this = undefined)
call 메서드를 활용해 this에 c 바인딩 => 정상
class C {
vals = [1, 2, 3];
logSquares() {
for (const val of this.vals) {
console.log(val * val);
}
}
}
const c = new C();
c.logSquares(); // this = c 바인딩
// 1
// 4
// 9
// 메서드를 새로운 변수에 할당하면?
const method = c.logSquares; // this = undefined
method();
console.log(this); // undefined
// vals를 읽을 수 없음
// this 바인딩 제어하기
const method2 = c.logSquares;
method2.call(c); // this = c 바인딩
// 1
// 4
// 9
📍Class에서 this 바인딩 문제 해결하기
ResetButton 클래스에서 onClick 메서드를 호출하면 this가 정의되지 않아 에러가 발생
declare function makeButton(props: {text: string, onClick: () => void }): void;
class ResetButton {
render() {
return makeButton({text: 'Reset', onClick: this.onClick});
}
// onClick 메서드를 바로 호출하면 this 바인딩 문제로 에러
onClick() {
alert(`Reset ${this}`);
}
}
✅해결방법 1
생성자에서 메서드에 this를 바인딩
onClick 메서드에 this가 바인딩되어 해당 인스턴스에 onClick 메서드가 새로 생성됨
인스턴스에서 생성된 onClick 메서드는 ResetButton 프로토타입의 onClick 메서드보다 앞에 놓이므로 (프로토타입 체인)
render 메서드의 this.onClick은 바인딩된 메서드를 참조
declare function makeButton(props: {text: string, onClick: () => void }): void;
class ResetButton {
constructor() {
this.onClick = this.onClick.bind(this); // 인스턴스가 만들어지면 onClick 메서드에 바로 this가 바인딩됨
}
render() {
return makeButton({text: 'Reset', onClick: this.onClick});
}
onClick() {
alert(`Reset ${this}`);
}
}
✅해결방법 2
더 간단하게 해결하려면 화살표 함수를 사용하면 된다
ResetButton의 인스턴스가 생성될 때마다 제대로 바인딩된 this를 가지는 새 함수를 생성
declare function makeButton(props: {text: string, onClick: () => void }): void;
class ResetButton {
render() {
return makeButton({text: 'Reset', onClick: this.onClick});
}
onClick = () => {
alert(`Reset ${this}`); // "this" 가 항상 인스턴스를 참조
}
}
📍예시 2
✅생성자에서 this를 추가하거나, 화살표 함수를 사용하여 this 바인딩 에러를 방지하자!
function addKeyListener(
el: HTMLElement,
// 콜백 함수 fn의 매개변수에 this를 추가하면 this 바인딩을 체크하므로 에러를 잡아낼 수 있음
fn: (this: HTMLElement, e: KeyboardEvent) => void
) {
el.addEventListener('keydown', e => {
fn(e);
// ~~~~~ The 'this' context of type 'void' is not assignable
// to method's 'this' of type 'HTMLElement'
});
}
class Foo {
registerHandler(el: HTMLElement) {
// 일반 함수에서는 에러 잡아낼 수 없음!
addKeyListener(el, function (e) {
this.innerHTML;
});
// 화살표 함수는 에러를 잡아낼 수 있음!
addKeyListener(el, e => {
this.innerHTML;
// ~~~~~~~~~ Property 'innerHTML' does not exist on type 'Foo'
});
}
}