JiSoo's Devlog

🎪 이벤트 위임 본문

카테고리 없음

🎪 이벤트 위임

지숭숭숭 2025. 10. 29. 22:04

 

 

간단한 프로젝트에서는 이벤트 또한 간단하게 구현이 가능합니다.

하지만 프로젝트 규모가 커진다면 어떨까요?

복잡한 컴포넌트가 많아지고 로직이 복잡해진다면

이벤트가 어디서 어떻게 이루어지는지 헷갈리기 시작합니다

 

특히 비슷한 요소들이 많은 화면이라면

각각에 이벤트 리스너를 등록하는 순간부터 성능은 떨어지기 시작합니다.

DOM마다 핸들러를 붙이는 방법은 직관적이지만 결국 메모리 낭비, 렌더링 지연 등의 문제로 이어질 수 있습니다.

 

이럴 때 필요한 게 바로 이벤트 위임입니다! 😎

 

간단하게 설명하면 하나의 부모 요소에서 모든 하위 요소의 이벤트를 감시하는 방법이에요.

코드 구조를 단순화하고 불필요한 리스너를 최소화하는 데 유용한 방법이죠!

 

 

 

🫧 이벤트 버블링

이벤트 위임을 이해하기 위해서는 이벤트 버블링을 먼저 알아야 하는데요.

브라우저에서 이벤트가 발생하면 가장 안쪽 요소부터 시작해 상위 요소로 전파됩니다.

<div id="parent">
  <button id="child">Click me!!!</button>
</div>

<script>
  document.getElementById("parent").addEventListener("click", () => {
    console.log("Parent");
  });

  document.getElementById("child").addEventListener("click", () => {
    console.log("Child");
  });
</script>

위 코드에서 버튼을 클릭하면

Child → Parent

순으로 출력됩니다.

 

이렇게 이벤트가 안쪽에서 바깥쪽으로 전파되는 과정을 버블링이라고 합니다.

 

이벤트 위임은 이 버블링 현상을 활용하는 방법이에요.

 

💡 이벤트 위임

자식 요소마다 이벤트를 등록하는 대신

공통 부모 요소에 단 하나의 이벤트 리스너만 등록하고

이벤트가 어디서 발생했는지 판단하고 처리하게 됩니다.

 

<ul id="list">
  <li>레몬</li>
  <li>사과</li>
  <li>포도</li>
</ul>

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

  list.addEventListener("click", (event) => {
    if (event.target.tagName === "LI") {
      console.log(`${event.target.textContent} 클릭됨`);
    }
  });
</script>

클릭된 요소가 <li>인지 확인한 뒤 동작을 수행하고 있습니다.

이렇게 하면 리스트가 아무리 늘어나도 리스너는 단 1개만 존재하게 되는 거죠!

 

 

 

🧪 성능차이 실험

예를 들어 버튼이 1000개가 있다고 가정해 볼게요

 

↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

 

1. 개별 등록 방식

buttons.forEach(btn => {
  btn.addEventListener("click", () => {
    console.log("clicked");
  });
});

이 방식은 1000개의 리스너가 모두 메모리에 등록됩니다.

렌더링 시 브라우저가 각 리스너를 추적하고 관리해야 하기 때문에 성능이 떨어지고 메모리 사용량이 커지게 됩니다.

🤯 🤯

 

2. 이벤트 위임 방식

document.body.addEventListener("click", (e) => {
  if (e.target.matches(".button")) {
    console.log("clicked");
  }
});

반면 이 방식은 단 하나의 리스너만 등록하면 되는데요.

버튼이 아무리 많아져도 부모가 이벤트를 감지하고 버블링을 통해 처리하기 때문에 리스너 관리 비용도 줄고 렌더링 효율이 높아집니다.

🤩 🤩

 

 

 

🔄 이벤트 전파 과정

이벤트 전파는 캡처링 → 타깃 → 버블링 순으로 이루어집니다.

window → document → html → body → 부모 → 자식 (캡처링)
자식 (이벤트 발생)
자식 → 부모 → body → html → document → window (버블링)

 

기본적으로 addEventListener는 버블링 단계에서 실행됩니다.

하지만 세 번째 인자로 { capture: true } 를 주면 캡처링 단계에서 실행됩니다.

element.addEventListener("click", handler, { capture: true });

 

 

 

🚨 이벤트 위임 시 주의할 점

1. event.target이 항상 기대하는 요소는 아닐 수 있다.

예를 들어 내부에 <span>이나 <i> 태그가 포함된 경우에

클릭한 시제 요소가 <li>가 아닐 수 있어요.

→ 이런 경우 closest() 메서드를 활용하면 안전합니다

const li = e.target.closest("li");
if (!li) return;

 

 

2. stopPropagation()이나 preventDefault()로 전파가 차단될 수 있다.

 

3. 폼 요소나 focus, blur 이벤트는 버블링 되지 않습니다.

→ 이 경우 focusin, focusout을 대신 사용하는 게 좋아요.

 

 

 

📕 프레임워크별 차이

React

가상 DOM을 통해 이미 이벤트 위임이 내부적으로 구현되어 있어요.

우리는 <button onClick={ } /> 처럼 작성하면 실제로는 루트 요소에 하나의 리스너만 등록되어 있답니다.

 

Vanilla JS

직접 addEventListener를 사용하는 경우, 위임을 명시적으로 구현해야 해요.

규모가 커질수록 하나의 리스너로 모든 자식을 제어하는 구조가 훨씬 효율적이랍니다.

 

 

 

🤓 구현 예시

 

드롭다운 닫기

document.addEventListener("click", (e) => {
  const dropdown = e.target.closest(".dropdown");
  if (!dropdown) hideAllDropdowns();
});

 

 

모달 닫기

document.body.addEventListener("click", (e) => {
  if (e.target.classList.contains("modal-overlay")) {
    closeModal();
  }
});

 

 

이렇게 이벤트 위임을 사용하면 코드를 더 간결하게 작성할 수 있고 유지보수성을 높일 수 있어요!!

 

 

이벤트 위임은 단순히 리스너만 줄이는 것에서 끝이 아니라 코드 구조를 단순화하고 성능을 최적화할 수 있는 패턴이에요.

실제로도 많이 사용하지만 모달, 리스트 등 반복되는 요소가 많은 UI를 구현할 때 이벤트 위임을 잘 활용한다면 훨씬 효율적인 코드를 작성할 수 있겠죠? 🌟😙

728x90