frontend/javascript

🟨 2-7. 이벤트 위임(Event Delegation) — 반복되는 이벤트 코드를 깔끔하게 정리하는 기술

mirabo01 2025. 11. 7. 08:49

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”라고 봐도 좋다.

이벤트는 많을수록 복잡해진다.
하지만, 위임은 단순함 속에서 강력함을 만든다.