frontend/javascript

🟨 2-15. 이벤트 위임 실전 활용 — 성능과 유지보수를 모두 잡는 동적 이벤트 처리법

mirabo01 2025. 11. 7. 08:52

1. 이벤트 위임이란 무엇인가

이벤트 위임(Event Delegation)은 말 그대로
“하위 요소의 이벤트를 상위 요소가 대신 처리하는 방식”이다.

HTML 구조 예시 👇

<ul id="menu">
  <li>HTML</li>
  <li>CSS</li>
  <li>JavaScript</li>
</ul>

보통은 이렇게 각 <li>에 클릭 이벤트를 하나씩 걸지만 —

document.querySelectorAll("li").forEach((item) => {
  item.addEventListener("click", () => console.log(item.textContent));
});

이건 비효율적이야.
<li>가 100개라면 이벤트 리스너도 100개 생기고,
동적으로 추가된 항목에는 작동하지 않아.

그래서 이렇게 바꾼다 👇

document.getElementById("menu").addEventListener("click", (e) => {
  if (e.target.tagName === "LI") {
    console.log("클릭된 메뉴:", e.target.textContent);
  }
});

✅ 이벤트는 한 번만 등록
✅ 새로 추가된 <li>도 자동으로 반응
✅ 코드 간결 + 메모리 절약

이게 바로 이벤트 위임(Event Delegation) 의 본질이다.


2. 이벤트 위임의 동작 원리

이벤트 위임이 가능한 이유는 버블링(Bubbling) 덕분이다.
즉, 하위 요소에서 발생한 이벤트가 상위로 전달되기 때문에
상위 요소에서 그 이벤트를 대신 감지할 수 있는 것이다.

<div id="parent">
  <button>클릭</button>
</div>
document.getElementById("parent").addEventListener("click", (e) => {
  console.log("이벤트 감지:", e.target.tagName);
});

→ button을 클릭해도, 이벤트는 parent까지 전달되어 실행된다.
→ 따라서 상위에서 하위를 “대신 감시”할 수 있다.


3. 실전 예제 — To-Do 리스트

아래 예시는 동적으로 생성된 항목에도
자동으로 이벤트가 작동되는 실무형 예시다.

<input id="taskInput" placeholder="할 일을 입력하세요" />
<button id="addBtn">추가</button>
<ul id="taskList"></ul>

<script>
const input = document.getElementById("taskInput");
const addBtn = document.getElementById("addBtn");
const list = document.getElementById("taskList");

addBtn.addEventListener("click", () => {
  const text = input.value.trim();
  if (!text) return alert("내용을 입력하세요!");
  const li = document.createElement("li");
  li.innerHTML = `${text} <button class="delete">삭제</button>`;
  list.appendChild(li);
  input.value = "";
});

list.addEventListener("click", (e) => {
  if (e.target.classList.contains("delete")) {
    e.target.parentElement.remove();
  }
});
</script>

✅ 리스트에 새 항목이 추가되어도 “삭제 버튼”이 자동 작동
✅ querySelectorAll로 새 버튼을 다시 탐색할 필요 없음

이게 바로 이벤트 위임의 강력한 장점이다.


4. 이벤트 위임의 장점

항목 설명

💡 성능 향상 리스너 개수가 줄어들어 메모리 절약
🔄 동적 요소 대응 Ajax나 DOM 추가 후에도 자동 적용
🧩 유지보수 편의성 중앙 집중식 제어로 코드 간결
React와 유사한 구조 실제 React도 이벤트 위임 방식으로 동작

5. 조건 분기와 타깃 확인

복잡한 구조에서도 e.target을 활용해
클릭된 요소의 정보를 정확히 구분할 수 있다.

<ul id="list">
  <li data-id="1"><span>HTML</span> <button class="edit">수정</button></li>
  <li data-id="2"><span>CSS</span> <button class="edit">수정</button></li>
</ul>

<script>
document.getElementById("list").addEventListener("click", (e) => {
  const item = e.target.closest("li");
  if (!item) return;

  if (e.target.classList.contains("edit")) {
    const id = item.dataset.id;
    console.log(`ID ${id} 수정 버튼 클릭`);
  } else if (e.target.tagName === "SPAN") {
    console.log(`항목 클릭: ${item.dataset.id}`);
  }
});
</script>

✅ closest()는 가장 가까운 상위 요소를 탐색 (React의 delegation 구조와 유사)
✅ dataset으로 각 항목의 고유 식별자 접근 가능


6. 성능 비교 — 직접 이벤트 vs 위임 이벤트

비교 항목 개별 이벤트 이벤트 위임

리스너 개수 N개 (요소마다) 1개
메모리 사용량 많음 적음
동적 요소 처리 새로 추가 시 다시 등록 필요 자동 적용
유지보수 어렵고 반복적 간결하고 체계적

➡ 리스트나 테이블, 갤러리처럼 요소가 많을수록 이벤트 위임의 효율이 폭발적으로 증가한다.


7. 이벤트 위임의 주의점

✅ e.target이 정확히 클릭한 요소를 반환하므로,
내부에 다른 태그가 있으면 의도치 않은 동작이 발생할 수 있다.

→ 이런 경우엔 closest() 또는 matches()로 범위를 제한하자.

if (e.target.closest("button.delete")) {
  // 삭제 버튼만 감지
}

✅ 이벤트가 버블링되지 않는 일부 이벤트에는 적용할 수 없다.
(예: focus, blur, mouseenter, mouseleave 등은 버블링 X)

대신 focusin, focusout 같은 버블링 가능한 대체 이벤트를 사용한다.


8. 실무 응용 — 카드형 UI 클릭 관리

<div id="cards">
  <div class="card" data-id="1"><h3>HTML</h3></div>
  <div class="card" data-id="2"><h3>CSS</h3></div>
  <div class="card" data-id="3"><h3>JavaScript</h3></div>
</div>

<script>
const cards = document.getElementById("cards");

cards.addEventListener("click", (e) => {
  const card = e.target.closest(".card");
  if (!card) return;
  alert(`${card.querySelector("h3").textContent} 카드 클릭됨`);
});
</script>

✅ 클릭된 카드만 인식
✅ 추가된 카드도 자동 반응
✅ 실제 React에서 카드 리스트를 map으로 그릴 때와 거의 동일한 구조


9. 이벤트 위임 + IntersectionObserver (고급 응용)

이벤트 위임은 “사용자 상호작용”뿐 아니라
스크롤 감지, 무한 스크롤, Lazy Loading 등과 결합해 성능 최적화에도 활용된다.

const list = document.getElementById("productList");

list.addEventListener("click", (e) => {
  if (e.target.matches(".load-more")) {
    loadMoreItems();
  }
});

→ 한 개의 상위 이벤트로 모든 하위 “로드 버튼” 관리 가능.


10. 정리

개념 설명

이벤트 위임 상위 요소가 하위 요소의 이벤트를 대신 처리
핵심 원리 이벤트 버블링
장점 성능 향상, 코드 간결, 동적 요소 대응
실무 활용 리스트, 카드, 모달, 메뉴, 무한 스크롤 등
주의점 focus, mouseenter 등 비버블 이벤트 예외 처리 필요

11. 마무리

이제 이벤트 위임의 구조를 완전히 이해했다면,
DOM의 이벤트를 “조작”하는 게 아니라 “관리”하는 개발자로 한 단계 성장한 거야.

이 패턴은 단순한 트릭이 아니라,
대규모 UI를 효율적으로 제어하는 근본적인 철학이야.

“이벤트를 줄이는 것은 코드의 수를 줄이는 게 아니라,
브라우저의 일을 줄이는 것이다.”