🟨 2-7. 이벤트 위임(Event Delegation) — 반복되는 이벤트 코드를 깔끔하게 정리하는 기술
1. 이벤트 위임이란?
DOM을 다루다 보면 이런 상황이 생긴다. 👇
<ul>
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
<li>React</li>
</ul>
이제 각각의 <li> 클릭 시 콘솔에 텍스트를 출력하고 싶다.
대부분 이렇게 코드를 쓴다.
document.querySelectorAll("li").forEach((li) => {
li.addEventListener("click", () => {
console.log(li.textContent);
});
});
이 코드는 잘 작동하지만 문제가 있다.
새로운 <li>가 추가되면 어떻게 될까?
const ul = document.querySelector("ul");
const newItem = document.createElement("li");
newItem.textContent = "Vue";
ul.appendChild(newItem);
👉 새로 추가된 항목은 클릭해도 이벤트가 동작하지 않는다.
왜냐하면 기존에 등록된 이벤트는 초기에 존재하던 요소들에만 바인딩되었기 때문이다.
이 문제를 해결하는 게 바로 이벤트 위임(Event Delegation) 이다.
2. 이벤트 위임의 개념
이벤트 위임은 부모 요소에 이벤트를 한 번만 등록하고,
이벤트가 하위 요소로부터 “버블링(Bubbling)” 되어 올라올 때
타겟을 식별해서 처리하는 방식이다.
const ul = document.querySelector("ul");
ul.addEventListener("click", (e) => {
if (e.target.tagName === "LI") {
console.log(`클릭한 항목: ${e.target.textContent}`);
}
});
✅ 이렇게 하면 <ul> 하나에만 이벤트를 등록했는데도
새로 추가된 <li>까지 자동으로 동작한다.
3. 이벤트 버블링(Bubbling)과 캡처링(Capturing)
DOM 이벤트는 두 가지 흐름을 가진다.
1️⃣ 캡처링 단계: window → document → body → 요소
2️⃣ 버블링 단계: 요소 → body → document → window
즉, 클릭이 발생하면
안쪽 요소에서 바깥쪽 요소로 이벤트가 전달된다.
이 특성을 이용해서
하위 요소의 이벤트를 상위에서 “위임(delegate)”하는 것이다.
4. 예시: 동적 TODO 리스트
HTML:
<ul id="todoList">
<li>자바스크립트 공부하기</li>
<li>운동하기</li>
</ul>
<input id="newTodo" placeholder="할 일 입력" />
<button id="addBtn">추가</button>
JS:
const list = document.getElementById("todoList");
const addBtn = document.getElementById("addBtn");
const input = document.getElementById("newTodo");
// 부모에 이벤트 등록
list.addEventListener("click", (e) => {
if (e.target.tagName === "LI") {
e.target.remove();
}
});
// 동적 항목 추가
addBtn.addEventListener("click", () => {
const li = document.createElement("li");
li.textContent = input.value;
list.appendChild(li);
input.value = "";
});
✅ 클릭하면 항목이 삭제된다.
✅ 새로 추가한 항목도 동일하게 작동한다.
✅ 단 한 번의 이벤트 등록으로 모든 자식이 제어된다.
5. 이벤트 위임의 장점
항목 설명
| 1️⃣ 성능 향상 | 수백 개의 요소에 각각 이벤트를 붙이지 않아도 됨 |
| 2️⃣ 유지보수 용이 | 새로 추가되거나 삭제되는 요소도 자동 적용 |
| 3️⃣ 코드 간결화 | 이벤트 등록 코드가 한 곳에 집중됨 |
| 4️⃣ 실무 친화적 | React/Vue의 “이벤트 버블링” 구조 이해에 직접 연결됨 |
6. e.target vs e.currentTarget 차이
이벤트 위임에서 자주 헷갈리는 두 객체가 있다.
구분 설명 예시
| e.target | 실제 클릭된 요소 | <li> 클릭 시 → LI |
| e.currentTarget | 이벤트가 등록된 요소 | <ul> |
ul.addEventListener("click", (e) => {
console.log("target:", e.target.tagName);
console.log("currentTarget:", e.currentTarget.tagName);
});
결과:
target: LI
currentTarget: UL
✅ 이 차이를 이해해야 올바르게 위임할 수 있다.
7. 조건부 위임 — 클래스 기반 필터링
위임은 무조건 모든 자식에게 이벤트가 전달되는 게 아니라,
특정 조건을 걸 수도 있다.
<ul id="menu">
<li class="edit">수정</li>
<li class="delete">삭제</li>
</ul>
const menu = document.getElementById("menu");
menu.addEventListener("click", (e) => {
if (e.target.classList.contains("edit")) {
alert("수정 기능 실행");
} else if (e.target.classList.contains("delete")) {
alert("삭제 기능 실행");
}
});
✅ 이 방식은 실제 관리자 페이지나 백오피스에서
테이블 행 클릭, 버튼 구분 등에서 자주 쓰인다.
8. 실무 예시: 테이블 행 클릭 이벤트
<table id="userTable">
<tr><td>1</td><td>홍길동</td></tr>
<tr><td>2</td><td>이순신</td></tr>
</table>
const table = document.getElementById("userTable");
table.addEventListener("click", (e) => {
const row = e.target.closest("tr");
if (!row) return;
const id = row.cells[0].textContent;
const name = row.cells[1].textContent;
alert(`선택한 사용자: ${name} (ID: ${id})`);
});
✅ closest()를 활용하면 부모 방향으로 탐색 가능.
✅ 실무에서 테이블이나 카드형 UI를 클릭할 때 자주 사용.
9. 이벤트 전파 제어 (stopPropagation)
특정 이벤트가 부모까지 전달되지 않게 하려면 e.stopPropagation() 사용.
document.body.addEventListener("click", () => console.log("BODY 클릭"));
document.querySelector(".box").addEventListener("click", (e) => {
e.stopPropagation();
console.log("BOX 클릭");
});
✅ 이렇게 하면 .box 클릭 시 BODY의 이벤트는 무시된다.
✅ 필요 이상으로 이벤트가 버블링되지 않게 제어 가능.
10. 정리
항목 내용
| 정의 | 하위 요소의 이벤트를 부모 요소에 위임하는 패턴 |
| 핵심 개념 | 이벤트 버블링(Bubbling) |
| 장점 | 성능 향상, 코드 단순화, 유지보수 용이 |
| 주요 속성 | e.target, e.currentTarget, closest() |
| 실무 응용 | 리스트, 테이블, 동적 UI, 관리자 페이지 |
11. 마무리
이제 당신은 수백 개의 버튼을 가진 화면도 단 한 줄로 제어할 수 있게 되었다.
이벤트 위임은 “프레임워크 없는 React”라고 봐도 좋다.
이벤트는 많을수록 복잡해진다.
하지만, 위임은 단순함 속에서 강력함을 만든다.