본문으로 바로가기
TaeyoungKim.dev

JavaScript 이벤트 버블링과 이벤트 위임: 동적으로 추가한 목록에도 클릭을 연결하는 법

웹작성 약 2분 읽기TaeyoungKim
LinkedInX

목록 버튼마다 클릭 리스너를 붙였는데 새 항목은 반응하지 않는다면, 새 DOM에는 리스너가 없기 때문이다. 이벤트 위임은 자식에서 발생한 이벤트가 부모로 올라오는 버블링을 이용해 이 반복을 줄인다.

부모 하나에 리스너를 등록한다

도식처럼 자식 버튼에서 시작된 클릭은 상위 목록까지 올라간다. 이 경로를 이용하면 항목마다 리스너를 붙이지 않고 부모 한 곳에서 대상을 구분할 수 있다.

html
<ul id="todos">
  <li><button data-action="remove">삭제</button> 첫 할 일</li>
</ul>
js
const todos = document.querySelector("#todos");

todos.addEventListener("click", (event) => {
  const button = event.target.closest("button[data-action='remove']");
  if (!button || !todos.contains(button)) return;
  button.closest("li")?.remove();
});

클릭은 button에서 시작해 li, ul로 올라온다. ul은 처음부터 존재하므로 나중에 추가한 li의 버튼 클릭도 받을 수 있다. closest를 쓰면 버튼 안의 아이콘이나 span을 눌러도 의도한 버튼을 찾을 수 있다.

첫 항목을 지우기 전에 아래 코드를 실행해 새 항목을 만들어 보자. 새 버튼에도 별도 리스너를 붙이지 않았지만, 같은 부모 리스너가 처리한다. 버튼 바깥 글자를 눌렀을 때는 항목이 남아 있어야 한다.

js
const item = document.createElement("li");
const button = document.createElement("button");
button.dataset.action = "remove";
button.textContent = "삭제";
item.append(button, " 두 번째 할 일");
todos.append(item);

target과 currentTarget을 구분한다

event.target은 실제로 눌린 가장 안쪽 요소이고, event.currentTarget은 리스너가 등록된 요소다. 위 예시에서 target은 아이콘일 수 있지만 currentTarget은 항상 ul이다. 이 차이를 모르면 버튼이 아닌 위치를 눌렀을 때도 삭제 로직이 실행되는 버그가 생긴다.

모든 이벤트에 위임이 정답은 아니다

입력 요소의 개별 상태처럼 각 요소가 독립적으로 관리돼야 하는 경우에는 직접 리스너가 더 읽기 쉬울 수 있다. 또한 이벤트 위임은 실제 상호작용 요소를 <button>으로 만들었다는 전제가 있어야 키보드 사용자도 같은 기능을 사용할 수 있다.

stopPropagation()은 부모의 동작을 막지만, 분석·닫기·접근성 기능처럼 상위에서 필요했던 이벤트까지 끊을 수 있다. 먼저 이벤트 흐름을 설계하고 꼭 필요한 컴포넌트 경계에서만 사용하자.

핵심 요약

이벤트 위임은 이벤트 버블링을 이용해 목록 부모에 리스너 하나만 두는 방법이다. 새로 추가한 항목도 자연스럽게 처리하지만, target과 currentTarget을 구분하고 closest로 의도한 요소인지 확인해야 한다. 클릭 가능한 UI는 div가 아니라 button으로 만들어 키보드 동작도 보장하자.

작성자

TaeyoungKim

기초 개념을 구현과 검증, 실제 운영 판단까지 연결해 기록합니다.

#JavaScript#이벤트 버블링#이벤트 위임

함께 읽으면 좋은 글