본문 바로가기
개발

[JS] Event Delegation(이벤트 위임)

by _Jun 2022. 1. 6.

이벤트 위임을 이해하기 위해서는 이벤트 버블링과 캡쳐링에 대한 이해가 필요합니다. 이벤트 버블링과 캡쳐링에 대해 잘 모르시는 분은 아래 글을 참고하시면 될 것 같습니다.

[JS] Event Bubbling과 Capturing

 

[JS] Event Bubbling과 Capturing

웹페이지에서 가장 중요한 것 중 하나를 뽑자면 사용자와의 인터렉션을 꼽을 수 있습니다. 인터렉션은 브라우저 이벤트로 구현할 수 있고, 주로 DOM 이벤트를 이용합니다. 이벤트에는 마우스,

hangeoreum.tistory.com

 


 

 1. 이벤트 위임(Event Delegation) 

이벤트 위임은 상위 요소에서 하위 요소의 이벤트를 제어하는 방식입니다. Vanilla JS로 웹페이지를 만들다 보면 다음과 같은 동작을 수행하고 싶은 경우가 종종 있습니다.

  • 특정 요소 아래에 있는 여러 하위 요소를 비슷한 방식으로 다루는 경우
  • 사용자의 인터렉션을 통해 추가되는, 아직 만들어지지 않은 요소에 이벤트 핸들러를 할당해야 하는 경우

위와 같은 경우에 이벤트 위임을 사용하면 문제를 손쉽게 해결할 수 있습니다.

 

이벤트 위임을 사용하면 상위 요소에 할당한 이벤트 핸들러로 여러 하위 요소를 다룰 수 있습니다. 이벤트 위임을 사용할 때는 하위 요소마다 이벤트 핸들러를 할당하지 않고, 상위 요소에 하나의 이벤트 핸들러를 할당합니다. 이벤트의 버블링 되는 특징을 이용하면 상위 요소에 있는 하나의 이벤트 핸들러만으로 하위 요소에서 발생한 이벤트들을 모두 처리할 수 있습니다. 

 

코드 예시

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>
const list = document.querySelector('ul');

list.addEventListener("click", (e) => {
  if (e.target.tagName === "LI") {
    console.log(e.target.innerText);
  }
});

 

이벤트 위임에서는 위 코드처럼 각각의 <li> 태그에 일일이 이벤트 핸들러를 할당하는 대신에, <li>들의 공통 부모 요소인 <ul> 태그에 하나의 이벤트 핸들러를 할당합니다. 이벤트 버블링으로 인해 <li>에서 발생한 이벤트가 <ul>로 전파되기 때문에, <li>에서 발생한 클릭 이벤트를 상위 요소 <li>에서 감지하고 처리할 수 있습니다. 위 코드의 경우 <li>를 클릭하면 클릭한 <li>의 텍스트가 콘솔에 출력됩니다.

 

 

이벤트 위임의 장점: 

  • 다수의 이벤트 핸들러를 할당하는 대신에 하나의 이벤트 핸들러만 할당하기 때문에, 코드가 단순해지고 메모리가 절약된다.
  • 요소가 추가되거나 제거되는 동작이 많은 경우에도 짧은 코드만으로 이벤트 처리가 가능하다.

이벤트 위임의 단점:

  • 이벤트 위임을 사용하기 위해 이벤트가 반드시 버블링 되어야 한다. focus 이벤트처럼 버블링 되지 않는 이벤트나, stopPropagation()을 사용한 경우에는 이벤트 위임을 사용할 수 없다.

 


 

 2. 이벤트 위임 활용 예시 

2-1. 사용자 인터렉션으로 미래에 추가될 요소에서의 이벤트 다루기

<ul>
  <li>First item</li>
  <li>Second item</li>
  <li>Third item</li>
</ul>
<input type="button" value="Create list item" />

 

const list = document.querySelector("ul");
const listItems = document.querySelectorAll("li");

listItems.forEach((item) => {
  item.addEventListener("click", (e) => {
    window.alert(e.target.innerText);
  });
});

// 새로운 <li> 추가
document.querySelector("input").addEventListener("click", (e) => {
  const newListItem = document.createElement("li");
  newListItem.innerText = "New item";
  list.appendChild(newListItem);
});

 

위 코드는 각각의 <li>에 이벤트 핸들러를 할당하고 버튼을 눌렀을 때 새로운 <li>를 추가하는 코드입니다. 기존에 있는 <li>에 대해서는 이벤트 핸들러가 잘 동작하지만, 새로 만들어지는 <li>에는 이벤트 핸들러가 동작하지 않습니다.

 

새로 추가한 리스트에서는 클릭 이벤트가 작동하지 않습니다.

 


 

새로 <li>를 추가하는 코드에 이벤트 핸들러를 할당하는 코드를 추가하면 해결 가능하지만, 매번 새로운 아이템에 이벤트 핸들러를 할당하는 것은 번거롭습니다. 이벤트 위임을 이용하는 경우 더 간단하게 해결 가능합니다.

 

const list = document.querySelector("ul");
const listItems = document.querySelectorAll("li");

list.addEventListener("click", (e) => {
  if (e.target.tagName === "LI") {
    window.alert(e.target.innerText);
  }
});

// 새로운 <li> 추가
document.querySelector("input").addEventListener("click", (e) => {
  const newListItem = document.createElement("li");
  newListItem.innerText = "New item";
  list.appendChild(newListItem);
});

 

위 코드처럼 <li>의 상위 요소인 <ul>에 이벤트 핸들러를 할당하면 새롭게 생기는 <li>에서 발생하는 이벤트도 잡아낼 수 있습니다.

새로 추가한 리스트에서도 클릭 이벤트가 잘 작동합니다.

 

2-2. HTML data-* 속성과 함께 사용하기 

data-* 속성과 함께 이벤트 위임을 사용하면 비슷한 동작을 수행하는 요소들을 다루는 경우 편리합니다.

 

<div>
  <input data-action="save" type="button" value="Save" />
  <input data-action="reset" type="button" value="Reset" />
  <input data-action="upload" type="button" value="Upload" />
</div>

 

const action = {
  save() {
    window.alert("Save");
  },
  reset() {
    window.alert("Reset");
  },
  upload() {
    window.alert("Upload");
  }
};

const buttonContainer = document.querySelector("div");
buttonContainer.addEventListener("click", (e) => {
  if (e.target.tagName === "INPUT") {
    action[e.target.dataset.action]();
  }
});

 

위 코드에서는 각각의 버튼을 클릭하는 경우 서로 다른 함수를 실행하는 동작을 이벤트 위임을 통해서 구현했습니다.

 

 

2-3. element.matches()와 함께 사용하기

지금까지 예시에서 이벤트가 발생한 요소를 확인하는 경우 tagName 속성을 주로 사용했습니다. 이벤트가 발생한 요소를 확인할 때, Element.matches()를 사용할 수도 있습니다. 단순히 태그의 이름만 사용하는 대신에 css selector 규칙을 통해서 더 구체적으로 요소를 확인할 수 있습니다.

 

예를 들어, active class를 갖는 input 태그에서 발생한 이벤트를 다루기 위해서는 

 

document.addEventListener("click", (e) => {
  if (e.target.matches("input.active")) {
    console.log("Active input element!");
  }
});

 

와 같은 코드를 사용할 수 있습니다.

 

 

 

 

References

https://ko.javascript.info/events

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events

https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/#%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B2%84%EB%B8%94%EB%A7%81---event-bubbling

UI Events (w3.org)

https://davidwalsh.name/event-delegate

댓글