[자바스크립트] 이벤트 위임을 활용한 Event Listener 중복 해결
드림코딩에서 '프론트엔드 필수 브라우저 101' 강의를 듣다가, 쇼핑 리스트 만들기 실습 문제를 풀어보았다.
input 창에 내용을 입력하고 추가 버튼을 누르거나 엔터 키를 누르면 목록에 아이템이 추가되고,
추가된 목록에서 오른쪽 삭제 버튼을 누르면 해당 아이템이 삭제되는 간단한 웹사이트이다.
추가/삭제 기능은 원하는 대로 이루어지지만, 위와 같이 삭제 버튼에 Event Listener가 여러 개 쌓이는 문제가 있었다.
이는 아이템을 추가할 때마다 document에서 삭제 버튼을 모두 찾고, 찾은 모든 삭제 버튼에 Event Listener를 각각 추가해주도록 구현했기 때문이다.
겉보기에는 문제가 없어보이지만, 현재 프로그램의 경우 아이템의 삭제가 한 번밖에 일어날 수 없기 때문에 멀쩡해 보이는 것뿐이다. Event Listener에 반복이 가능한 코드를 넣으면 한 번 일어나야 할 작업이 여러 번 일어날 것이다. 또한 메모리가 낭비되는 문제도 있다.
💡 참고로 요소에 등록된 Event Listener는 [ 크롬 개발자 도구 → Elements → Event Listeners ] 에서 확인 가능하다.
// 기존 코드
function addItem(inputText) {
// 아이템을 list에 추가하는 부분 (생략)
}
// Event Listener 중복을 야기한 부분
function addEventToDelBtns() {
const delBtns = document.querySelectorAll('.del');
delBtns.forEach((delBtn) => {
delBtn.addEventListener('click', () => {
const itemToDelete = delBtn.parentElement;
itemToDelete.remove();
});
});
}
function addItemAndResetInput() {
if (input.value === '')
return;
addItem(input.value);
input.value = '';
addEventToDelBtns();
}
addBtn.addEventListener('click', addItemAndResetInput);
input.addEventListener('keypress', (key) => {
if (key.key == 'Enter') {
addItemAndResetInput();
}
});
이런 코드를 작성한 이유는, Event Listener를 정확히 아이템의 '삭제 버튼'에 등록해야 한다고 생각했기 때문이다.
하지만 아이템(li)들을 모두 감싸는 ul 요소에만 Event Listener를 등록한 후, 클릭이 삭제 버튼에서 일어났을 때 해당 삭제 버튼의 부모 요소를 삭제하면 Event Listener 중복 없이 원하는 기능이 구현된다.
// 추가한 코드
// 기존 코드에서 addEventToDelBtns는 모두 지운다.
list.addEventListener('click', (e) => {
if (e.target.className === 'del') {
const itemToDelete = e.target.parentElement;
itemToDelete.remove();
}
});
여기서 이벤트 위임(Event Delegation)의 개념이 활용되었다.
이벤트 위임은 여러 개의 하위 DOM 요소에 각각 이벤트 핸들러를 등록하는 대신, 하나의 상위 DOM 요소에 이벤트 핸들러를 등록하는 방법을 말한다.
자바스크립트에서 한 요소(event.target)에 이벤트가 발생하면 해당 요소(event.target)에 할당된 핸들러가 먼저 동작하고 그다음 부모 요소, 또 그다음 부모 요소에 할당된 핸들러가 차례로 동작한다. (마치 마트료시카 인형처럼..🪆)
이를 버블링(Bubbling)이라고 하며, 거의 모든 이벤트는 버블링된다. (⚠️ 버블링되지 않는 이벤트도 있다.)
따라서 공통 상위 요소에만 핸들러를 할당해도 원하는 동작을 구현할 수 있는 것이다.
다음 동작 예시와 코드를 보면 이해할 수 있을 것이다.
<section>
<article>
<div>
<p>Hello world</p>
</div>
</article>
</section>
const section = document.querySelector('section');
const article = document.querySelector('article');
const div = document.querySelector('div');
const p = document.querySelector('p');
section.addEventListener('click', () => {
console.log('section!');
});
article.addEventListener('click', () => {
console.log('article!');
});
div.addEventListener('click', () => {
console.log('div!');
});
p.addEventListener('click', () => {
console.log('p!');
});
참고를 위해 전체 코드도 GitHub에 업로드했습니다. (링크)
혹시 이해되지 않는 부분이나 오류가 있을 시 댓글 남겨주시면 감사하겠습니다. 🙇🏻
References
모던 자바스크립트 Deep Dive (Chapter 40.7)
'프로그래밍 > JavaScript' 카테고리의 다른 글
[JS] 이벤트 객체의 target, currentTarget 차이 (0) | 2022.04.19 |
---|---|
[JS] 얕은 복사, 깊은 복사 차이 쉽게 이해하기 (3) | 2022.03.31 |
[JS] 단축 평가(Short-Circuit Evaluation) 쉽게 이해하기 | &&, ||, 연산자 우선순위 (0) | 2022.03.07 |
[자바스크립트] class vs object, 객체지향 언어 클래스 정리 (0) | 2021.03.22 |
[자바스크립트] Arrow function (0) | 2021.03.22 |