이벤트 위임(event delegation)

임철종·2022년 7월 14일
1
post-thumbnail
post-custom-banner

이벤트 위임이란?

이벤트 위임(event delegation)이란 Event capturing & bubbling 에서 정리했던 이벤트의 단계중 bubbling을 활용하여 구현하는 강력한 이벤트 핸들링 패턴입니다.

MDN - Event delegation
버블링은 또한 이벤트 위임의 이점을 취할 수 있게 합니다 — 이 개념은 만약 다수의 자식 요소 중 하나를 선택했을 때 코드를 실행하기를 원한다면, 모든 자식에 개별적으로 이벤트 리스너를 설정해야만 하는 것 대신 이벤트 리스너를 그들의 부모에 설정하고 그들에게서 일어난 이벤트가 그들의 부모에게까지 올라오게 할 수 있다는 사실에 의존합니다. 기억하세요, 버블링은 이벤트 핸들러에 대해 이벤트가 발생된 요소를 먼저 검사하고서, 요소의 부모 등등으로 올라가는 것을 포함합니다.

MDN에서 나와있듯 비슷한 방식으로 여러 요소의 이벤트를 다뤄야 할 때 사용할 수 있습니다.
이벤트 위임을 사용하면 요소마다 핸들러를 할당하지 않고 공통 부모 혹은 조상에게 핸들러를 하나만 할당하여 여러 요소를 한번에 다룰 수 있습니다.

Bubbling 덕분이죠!


예시1

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <link rel="stylesheet" href="https://unpkg.com/mvp.css">
  <title>Event delegation</title>
</head>
<body>
<main>
  <header>
    <nav>
      <ul>
        <li><a href="#">Apple🍎</a></li>
        <li><a href="#">Banana🍌</a></li>
        <li><a href="#">Orange🍊</a></li>
        <li><a href="#">Avocado🥑</a></li>
        <li><a href="#">Strawberry🍓</a></li>
        <li><a href="#">Kiwi🥝</a></li>
      </ul>
    </nav>
  </header>
</main>
</body>
</html>

여기 과일 리스트가 있습니다.
과일 리스트의 과일을 선택하면 선택한 과일의 이름을 빨간색으로 강조하려면 어떤 방법을 쓸 수 있을까요?


좋지 않은 예시 💩

클릭이 될 각 a태그에 이벤트 리스너를 등록하여 스타일을 변경하면 되겠죠?

...
<style>
    .selected {
        color: red;
    }
</style>
...
<script>
    const items = document.querySelectorAll('a')
    items.forEach(item => item.addEventListener('click', () => item.classList.add('selected')))
</script>

업로드중..

이렇게 하면 원하는 것을 구현 할 수 있으나, 만약 리스트가 10000개, 100000개라면 어떨까요?
아이템에 이벤트 리스너를 각각 만들어서 등록해야 하므로 굉장히 성능적으로 좋지 않습니다.


좋은 예시(event delegation) ✨

이것을 해결하기 위해서는 event delegation을 사용할 수 있습니다.
event bubbling을 사용하여 하나의 리스너로 각 아이템의 이벤트를 다루는 것이죠.

...
<script>
    const fruitList = document.querySelector('ul')
    fruitList.addEventListener('click', (event) => {
        if (event.target !== fruitList) {
            event.target.classList.add('selected')
        }
    })
</script>

이렇게 부모 요소인 ul에 리스너를 등록하고, 타겟에 스타일을 적용하도록 하면 리스너를 하나만 등록하고도 여러 요소를 다룰 수 있습니다.


예시2

...
    <nav>
      <ul>
        <li>
          <button>저장하기</button>
        </li>
        <li>
          <button>불러오기</button>
        </li>
        <li>
          <button>삭제하기</button>
        </li>
      </ul>
    </nav>

'저장하기', '불러오기', '삭제하기' 기능을 가진 메뉴를 구현하려고 합니다.
각 버튼의 기능을 가진 메서드(save, load, delete)가 있는 객체는 구현되어 있다고 가정해보겠습니다.
버튼과 메서드를 어떻게 연결할 수 있을까요?
위에서 본 것처럼 버튼에 각각 리스너를 등록하는 것은 그리 좋지 않은 방법입니다.

우아한 방법은 바로 메뉴 전체에 리스너를 등록하고, 각 버튼의 커스텀 속성에 호출할 메서드를 넣는 것입니다.

 ...
    <nav id="menu">
      <ul>
        <li>
          <button data-action="save">저장하기</button>
        </li>
        <li>
          <button data-action="load">불러오기</button>
        </li>
        <li>
          <button data-action="delete">삭제하기</button>
        </li>
      </ul>
    </nav>
 ...
<script>
  class Menu {
    constructor(element) {
      this._element = element;
      element.onclick = this.onClick.bind(this);
      // this 바인딩하지 않으면 Menu 객체가 아닌 DOM 객체를 참조하게 되므로
      // this[action]을 찾을 수 없음
    }

    save() {
      alert('저장하기');
    }

    load() {
      alert('불러오기');
    }

    delete() {
      alert('삭제하기');
    }

    onClick(event) {
      let action = event.target.dataset.action;
      if (action) {
        this[action]();
      }
    };
  }

  new Menu(menu);
 </script>

이러한 방법을 사용하면

  • 각 버튼마다 리스너를 등록하지 않고 button태그에 원하는 메서드를 써주기만 하면 된다.
  • 버튼을 쉽게 추가하고 제거할 수 있으므로 HTML 구조가 유연해진다.

요약

이벤트 위임은 다음과 같은 알고리즘으로 동작한다.
1. 부모(조상)요소에 리스너, 핸들러를 등록한다.
2. 리스너, 핸들러의 event.target을 사용해 이벤트가 발생한 요소를 찾는다.
3. 원하는 요소에서 이벤트가 발생했다면 이벤트를 핸들링한다.

장점

  • 많은 리스너, 핸들러를 등록하지 않아도 되기 때문에 초기화가 단순해지고 메모리가 절약된다.
  • 요소를 추가하거나 제거할 때 해당 요소에 할당된 리스너, 핸들러를 추가하거나 제거할 필요가 없다.

단점

  • 이벤트가 반드시 버블링되어야 하지만 몇 이벤트는 버블링 되지 않는다. ex) focus
  • 부모(조상)요소에 할당된 리스너, 핸들러가 응답할 필요가 모든 하위 컨테이너에서 발생하는 이벤트에 응답해야 하므로 CPU 작업 부하가 늘어날 수 있습니다. 그런데 이런 부하는 무시할만한 수준이므로 실제로는 잘 고려하지 않습니다.

참고

모던 JavaScript 튜토리얼
MDN - Event delegation

profile
🌑🌘🌗🌖🌕
post-custom-banner

0개의 댓글