우아한테크코스 로또 미션을 구현하며 web과 코드 사이의 상호작용을 컨트롤러에서 담당했는데,
그 과정에서 this 바인딩 에러가 발생했다.
원인을 한 마디로 정리하자면, addEventListener 속성에서의 this 때문!
class LottoWebController {
#webBudget;
async start() {
document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget()); // 문제가 발생한 곳
}
getWebBudget() {
const webBudgetInput = document.querySelector("#budget").value;
this.#webBudget = Number(webBudgetInput);
}
/**
* 예산 입력 관리 및 유효성 검사
* @param {event} e
*/
handleWebBudget(event) {
event.preventDefault();
this.getWebBudget();
}
}
처음 작성했던 코드에서는 addEventListener의 함수를 화살표 함수 형태가 아닌 메서드를 직접 호출하는 방식을 사용했다.
이때, 해당 id값에서 submit 이벤트가 발생했을 때가 아닌 클래스 인스턴스가 생성되자마자 handleWebBudget이 호출된다.
document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget());
handleWebBudget(event) {
event.preventDefault();
this.getWebBudget();
}
getWebBudget() {
const webBudgetInput = document.querySelector("#budget").value;
this.#webBudget = Number(webBudgetInput);
}
#budget 에는 값이 입력되지 않은 상태에서 webBudget을 저장하게 된다.
하지만 #budget에 값을 입력한 뒤 submit을 할 때 webBudget에 값을 저장해야 하는 상황!
# 해결 방법 1
- 화살표 함수 추가
class LottoWebController {
#webBudget;
async start() {
document.querySelector("#content-box-input-budget").addEventListener("submit", () => this.handleWebBudget()); // 화살표 함수
}
getWebBudget() {
const webBudgetInput = document.querySelector("#budget").value;
this.#webBudget = Number(webBudgetInput);
}
/**
* 예산 입력 관리 및 유효성 검사
* @param {event} e
*/
handleWebBudget(event) {
event.preventDefault();
this.getWebBudget();
}
}
콜백 함수 방식으로 사용하면 #budget 값을 입력한 뒤 비동기적으로 이벤트를 실행시킬 수 있다
# 해결 방법 2
class LottoWebController {
#webBudget;
async start() {
document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget.bind(this)); // 바인딩
}
getWebBudget() {
const webBudgetInput = document.querySelector("#budget").value;
this.#webBudget = Number(webBudgetInput);
}
/**
* 예산 입력 관리 및 유효성 검사
* @param {event} e
*/
handleWebBudget(event) {
event.preventDefault();
this.getWebBudget();
}
}
document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget.bind(this));
handleWebBudget 위치에 콜백 함수를 사용하지 않고자 한다면, 바인딩을 통해 this를 넘겨주면 된다.
document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget.bind());
만일 위의 경우처럼 앞의 this만 남겨두고 인자로 this를 넘겨주지 않는다면, handleWebBudget 내부의 this.getWebBudget() 메서드가 실행되지 않는다.
addEventListener에서의 this는 DOM 요소에서 #content-box-input-budget id 값을 갖는 요소를 의미한다.
즉, 앞의 this를 console로 찍어보면
<input id="content-box-input-budget" />
이 출력되게 된다.
따라서 bind를 했지만 this를 넘겨주지 않는 경우, handleWebBudget 내부에서의 this는 undefined가 출력된다.
handleWebBudget() {
console.log(this) // undefined
}
아래와 같이 바인딩 + this를 넘겨주게 되면, class가 handleWebBudget으로 잘 넘어가게 된다
document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget.bind(this));
즉, this.handleWebBudget.bind(this) 문장이 있는 위치에서 this는 class를 의미하는데, 이 class를 handleWebBudget에 전달하겠다는 의미.
=> handleWebBudget 내부에서 this를 찍어보면 class가 나온다.
handleWebBudget() {
console.log(this) // class LottoWebController
}
handleWebBudget에서 this가 잘 바인딩되어 class를 가리키고 있으므로,
this.getWebBudget() 메서드가 정상적으로 실행된다.
handleWebBudget(event) {
event.preventDefault();
this.getWebBudget();
}
정리
정상 동작하는 경우
document.querySelector("#budget").addEventListener("keydown", this.handleWebBudget.bind(this));
- 앞쪽의 this : 상위 스코프를 가리킴 (여기서는 class)
- 뒤쪽의 this : class 전달 ⇒ handleWebBudget 메서드에서 class 내의 다른 메서드를 호출해야 하는 경우, this가 class를 가리키게 되어 다른 메서드를 정상적으로 사용할 수 있다 (다른 메서드를 내부에서 호출하지 않는 경우 뒤쪽의 this 생략 가능)
document.querySelector("#budget").addEventListener("keydown", (e) => this.handleWebBudget(e));
- addEventListener 시 발생하는 event를 메서드에 전달하는 방식
- 앞쪽의 this는 상위 스코프인 class를 가리킴
- 뒤쪽의 e는 keydown event를 의미
정상 동작하지 않는 경우
document.querySelector("#budget").addEventListener("keydown", this.handleWebBudget());
- this : budget id 값을 갖는 DOM 요소를 가리킴
- keydown을 했을 때 handleWebBudget 이 실행되는 것이 아니라, js 파일이 실행되면서 바로 handleWebBudget 가 실행된다
bind 사용 이유
addEventListener에서 메서드 앞에 붙는 this가 DOM 요소가 아닌 class를 가리키도록 하기 위해
bind에서 this를 인자로 넘겨주는 이유
- this.handleWebBudget.bind(this) : handleWebBudget 안에서 this가 class가 된다
- 즉, this.handleWebBudget.bind(this) 문장이 있는 위치에서의 this는 class를 의미하는데, 이 class를 handleWebBudget 에 전달하겠다는 의미
- 따라서 handleWebBudget 메서드 내부에서 console.log(this) 는 class를 가리킨다
- this.handleWebBudget.bind() : this로 넘겨주는 인자가 없음
- handleWebBudget 메서드 내부에서 console.log(this) 는 undefined
PR 메세지 참고
'우아한테크코스' 카테고리의 다른 글
심리적 안전감에 필요한 것들은 무엇일까 (0) | 2024.08.18 |
---|---|
레벨 3 2차 스프린트 회고 - 서로의 온도를 맞추기 (0) | 2024.07.29 |
프론트엔드 React 프로젝트 S3로 배포하기 (1) | 2024.07.21 |
레벨 2 회고 - React를 '잘' 활용해 보자! (0) | 2024.06.16 |
레벨 1 회고 - 새로운 환경에 빠르게 적응하기, Vanilla JS부터 파헤치기 (1) | 2024.04.15 |