오늘은 Javascript 이벤트 위임이란 무엇인지, 이벤트의 전파를 중단하는 방법은 어떤 것이 있는지 알아보려고 합니다. 아주 간단한 상황에서는 앞선 글에서 소개한 이벤트 제어 방법으로도 충분하지만 조금 더 효율적, 효과적으로 이벤트를 다루기 위해선 반드시 이벤트 위임(Event Delegation) 패턴을 알아두어야 합니다.
1. 이벤트 위임이 필요한 이유
이벤트 위임이 무엇인지 알아보기 전, 이벤트 위임이란 것이 왜 필요한지 먼저 예제를 통해 알아보겠습니다.
<ul id="parent-list">
<li id="list-1">Item 1</li>
<li id="list-2">Item 2</li>
<li id="list-3">Item 3</li>
<li id="list-4">Item 4</li>
<li id="list-5">Item 5</li>
<li id="list-6">Item 6</li>
</ul>
let list = document.querySelectorAll("li");
list.forEach(function(li) {
li.addEventListener("click", function(e) {
console.log(e.target.id);
});
});
// 여기서 e는 이벤트 객체, target은 실제 이벤트가 발생한 요소를 의미합니다.
반복문을 통해 모든 li 요소에 이벤트 핸들러를 등록하고, li를 클릭했을 때, 해당 li의 id 속성을 화면에 출력하는 예제입니다. 위 코드는 큰 문제없이 잘 작동할 겁니다. 그럼 예제를 조금 더 확장해 보겠습니다.
<button id="add">추가 버튼!!</button>
<ul id="parent-list">
<li id="list-1">Item 1</li>
<li id="list-2">Item 2</li>
<li id="list-3">Item 3</li>
<li id="list-4">Item 4</li>
<li id="list-5">Item 5</li>
<li id="list-6">Item 6</li>
</ul>
let add_btn = document.querySelector("#add");
let parent = document.querySelector("#parent-list");
let list = document.querySelectorAll("li");
// 새로운 li 요소의 추가
add_btn.addEventListener("click", function() {
let new_li = document.createElement("li");
new_li.setAttribute("id", "list-7");
new_li.innerText = "새로운 리스트";
parent.appendChild(new_li);
});
list.forEach(function(li) {
li.addEventListener("click", function(e) {
console.log(e.target.id);
});
});
버튼을 클릭하면 새로운 li 요소를 하나 생성하는 이벤트 핸들러가 추가 됐습니다. 예제를 실행해 볼까요?
결과에서 볼 수 있듯이 기존에 존재하던 요소의 경우 이벤트 핸들러가 잘 동작하지만, 새로 생성된(동적으로 생성된 요소) li는 클릭해도 아무런 동작을 하지 않습니다. 의도대로 동작하지 않는 이유는 간단합니다. 반복문으로 이벤트 핸들러를 등록할 시점에는 새로운 li가 아직 존재하지 않았기 때문입니다!
이런 문제를 해결할 수 있는 것이 바로 이벤트 위임입니다.
동적 요소의 이벤트 제어 외에도 기존 이벤트 제어 방식은 아래와 같은 문제가 발생할 수 있습니다.
- 코드의 유지보수 비용 증가
- 중복된 이벤트 핸들러로 인한 메모리 사용의 비효율성
2. 이벤트 위임이란
이벤트 위임이란, '이벤트 제어의 역할을 각 하위요소가 아닌 상위 요소에게 맡기는 방식'이라고 할 수 있습니다. 앞에서 다뤘던 예제를 이벤트 위임 방식으로 개선해 보겠습니다.
<html>
<body>
<button id="add">추가 버튼!!</button>
<ul id="parent-list">
<p id="p태그">paragraph</p>
<li id="list-1">Item 1</li>
<li id="list-2">Item 2</li>
<li id="list-3">Item 3</li>
<li id="list-4">Item 4</li>
<li id="list-5">Item 5</li>
<li id="list-6">Item 6</li>
</ul>
</body>
</html>
let parent = document.querySelector("#parent-list");
///... 생략
// 수정 이전 내용
// list.forEach(function(li) {
// li.addEventListener("click", function(e) {
// console.log(e.target.id);
// });
// });
// 수정된 내용
parent.addEventListener("click", function(e) {
console.log(e.target.id);
});
이벤트 제어역할을 하위요소(li)가 아닌 상위 요소(ul)에게 맡기는 방식으로 수정하였습니다.
target은 우리가 실제 클릭 이벤트를 발생시킨 요소이기 때문에 li의 id가 출력됩니다.
동적으로 추가된 요소에서도 이벤트 핸들러가 잘 동작합니다! 이처럼 우리는 이벤트 핸들러를 각 하위 요소마다 등록하는 것이 아니라 상위 요소에 등록함으로써 동적인 요소의 이벤트 제어를 할 수 있게 되었습니다.
원리
이벤트의 전파에 대해 기억하시나요? 이벤트 위임은 바로 이벤트 전파의 특성을 이용한 패턴입니다. 위 예제에서 우리는 상위 요소인 ul 태그에 이벤트 핸들러를 등록하였습니다. 그리고 하위 요소인 li를 클릭했죠. 이벤트 전파 흐름에 따라 내부적으로는 이런 식으로 이벤트가 전파됨을 예상할 수 있습니다.
- 상위 요소 ... -> li : 캡쳐링 단계
- li : 타겟 단계
- li -> ul -> ... 상위 요소 : 버블링 단계
즉, 하위 요소를 클릭하더라도 이벤트의 전파에 의해 상위 요소로 이벤트가 전파되기 때문에 상위 요소에서 하위 요소의 이벤트 발생을 감지하고 제어할 수 있다는 것입니다.
생각해 볼 점
마지막 예제에서 ul의 하위에 li, p 두 가지 요소가 존재하고 있습니다. 하위 요소 중 특정 요소에서 발생한 이벤트만 제어하고 싶을 때는 어떻게 해야 할까요? 아래와 같은 방식으로 구현 가능합니다!
parent.addEventListener("click", function(e) {
if (e.target.nodeName == "LI") console.log(e.target.id);
});
이벤트가 발생한 target이 내가 원하는 요소인지 검사하는 코드를 추가하면 끝!
이벤트 전파 중단 & 이벤트 기본 동작의 취소
이벤트의 마지막 내용으로 이벤트 전파 중단과 이벤트 기본 동작의 취소에 대해 알아보겠습니다.
이벤트 전파 중단
아래의 예를 살펴봅시다.
<div class="parent" style="background: black">
<button class="child">클릭!</button>
</div>
let parent = document.querySelector(".parent");
let child = document.querySelector(".child");
parent.addEventListener("click", function(event) {
console.log("parent click!");
});
child.addEventListener("click", function(event) {
console.log("child click!");
});
이벤트 전파에 관한 내용을 떠올리며 결과를 예상해 보면, 버튼을 클릭했을 때 이벤트 버블링에 의해 button과 div의 이벤트 핸들러가 모두 동작할 것 같습니다.
예상한 대로 div를 클릭했을 때는 div의 이벤트 핸들러만 동작하고, button을 클릭했을 때는 div와 button의 이벤트 핸들러가 모두 동작했습니다. 만약 버튼을 클릭했을 때, 버튼의 이벤트 핸들러만 동작하게 하고 싶다면 어떻게 하면 될까요?
stopPropagation 메서드를 사용하여 간단하게 구현할 수 있습니다. 해당 메서드는 더 이상 이벤트의 전파가 진행되지 않게 해 줍니다. 버튼의 이벤트 핸들러에서 stopPropagation 메서드를 호출하면 이벤트 버블링 단계가 진행되는 것을 막아 원하는 결과를 얻을 수 있습니다.
child.addEventListener("click", function(event) {
event.stopPropagation();
console.log("child click!");
});
이벤트 기본 동작의 취소
웹브라우저의 요소들은 기본적인 동작 방식을 가지고 있습니다. 특정 이벤트가 발생했을 때 동작 방식이 미리 정의되어 있는 것이라고 이해하시면 됩니다.
- a 태그를 클릭하면 해당 페이지가 열림
- form 안에서 submit 이벤트가 발생하면 데이터를 전송
- checkbox를 클릭하면 체크가 됨
이렇게 미리 정의된 이벤트 기본 동작은 preventDefault 메소드를 사용하여 취소할 수 있는데, 사용법은 아주 간단합니다.
<form>
<input type="checkbox" />
<label for="checkbox">체크박스</label>
</form>
document.querySelector("input[type=checkbox]").addEventListener("click", function(e) {
console.log(e.cancelable);
e.preventDefault();
});
인라인 이벤트 핸들러 방식과 이벤트 핸들러 프로퍼티 방식의 경우는 return false를 실행하여 같은 효과를 얻을 수 있습니다.
오늘 다룬 내용들은 실제 개발 도중 마주치는 여러 상황들에서 유용하게 사용되는 개념인 만큼 꼭 이해하고 넘어가시면 좋을 것 같습니다. 이벤트와 관련된 아래 글들도 확인해 보세요!
'Javascript' 카테고리의 다른 글
Javascript 실행 컨텍스트, 호이스팅, 클로저의 이해(2) (0) | 2023.02.11 |
---|---|
Javascript 실행 컨텍스트, 호이스팅, 클로저의 이해(1) (0) | 2023.02.09 |
자바스크립트 반복문 forEach, for-in, for-of 비교 (1) | 2023.01.25 |
[Javascript] Javascript event, 이벤트 전파란? (0) | 2023.01.03 |
[Javascript] Javascript event, Event Handler란 무엇인가 (0) | 2023.01.02 |
댓글