이벤트 위임

2024. 3. 1. 23:22자바스크립트

이벤트 위임은 여러 개의 하위 DOM 요소에 각각 이벤트 핸들러를 등록하는 대신 하나의 상위 DOM 요소에 이벤트 핸들러를 등록하는 방법을 말한다.

이벤트 위임을 이해하기 위해서는 이벤트 캡처링이벤트 버블링을 이해해야한다. 특히 버블링을 유심히 살펴보자.

이벤트 전파

ul 태그의 두번째 li에 클릭 이벤트가 발생했다.

이벤트가 발생한 DOM 요소를 이벤트 타겟이라고 부른다.

이벤트가 발생하면 DOM 트리를 타고 이벤트 타겟까지 캡쳐링된다.📸

이벤트가 어디서 발생했지? 하면서 DOM 트리를 타고 내려오는 것이다.

 

그런 다음, 이벤트가 발생한 타겟을 찾았으면 다시 상위 DOM 요소로 이벤트가 올라가는데

이 과정을 버블링이라고 한다.💦

정리하면 이벤트 전파는 캡쳐링 ➡ 타겟 ➡ 버블링의 과정을 거친다.

🚀currentTarget과 target
이벤트 객체에서 currentTarget 프로퍼티와 target 프로퍼티가 헷갈릴 수 있다.
정리하자면 currentTarget은 현재 이벤트 핸들러가 달려있는 요소이고,
target은 이벤트가 발생한 요소이다.

 

<div>
  <ul>
    <li>예시</li>
  </ul>
</div>
document.querySelector('li').addEventListener('click', () => console.log('li 클릭'));
document.querySelector('ul').addEventListener('click', () => console.log('ul 클릭'));
document.querySelector('div').addEventListener('click', () => console.log('div 클릭'));

이렇게 이벤트 핸들러를 요소마다 달아놓고 li 태그를 클릭한다면 어떤 순서로 호출될까?

li 클릭
ul 클릭
div 클릭

 

다음과 같은 순서로 호출된다. 이벤트 전파의 기본방식은 버블링이라는 것이다.

이것을 캡쳐링으로 바꿀 수도 있다.

document.querySelector('ul').addEventListener('click', () => console.log('ul 클릭'), { capture: true });

이렇게 하면 다음과 같은 순서로 호출된다.

ul 클릭
li 클릭
div 클릭

캡쳐링으로 설정한 요소는 캡쳐링 단계에서 이벤트가 발생했다고 호출되고, 나머지는 버블링 단계에서 호출된다.

 

이렇게 이벤트는 이벤트를 발생시킨 이벤트 타깃은 물론 상위 DOM 요소에서도 캐치할 수 있다.

이벤트 위임

<!DOCTYPE html>
<html>
<head>
  <style>
    #fruits {
      display: flex;
      list-style-type: none;
      padding: 0;
    }

    #fruits li {
      width: 100px;
      cursor: pointer;
    }

    #fruits .active {
      color: red;
      text-decoration: underline;
    }
  </style>
</head>
<body>
  <nav>
    <ul id="fruits">
      <li id="apple" class="active">Apple</li>
      <li id="banana">Banana</li>
      <li id="orange">Orange</li>
    </ul>
  </nav>
  <div>선택된 내비게이션 아이템: <em class="msg">apple</em></div>
  <script>
    const $fruits = document.getElementById('fruits');
    const $msg = document.querySelector('.msg');

    // 사용자 클릭에 의해 선택된 내비게이션 아이템(li 요소)에 active 클래스를 추가하고
    // 그 외의 모든 내비게이션 아이템의 active 클래스를 제거한다.
    function activate({ target }) {
      [...$fruits.children].forEach($fruit => {
        $fruit.classList.toggle('active', $fruit === target);
        $msg.textContent = target.id;
      });
    }

    // 모든 내비게이션 아이템(li 요소)에 이벤트 핸들러를 등록한다.
    document.getElementById('apple').onclick = activate;
    document.getElementById('banana').onclick = activate;
    document.getElementById('orange').onclick = activate;
  </script>
</body>
</html>

이 코드의 단점은 무엇인가?

만약 내비게이션 아이템이 100개라면 100개의 이벤트 핸들러를 달아줘야한다.

 

이벤트는 이벤트를 발생시킨 이벤트 타깃은 물론 상위 DOM 요소에서도 캐치할 수 있다는 사실을 기억하자.

그렇다면 내비게이션 아이템에 이벤트가 발생했을 때 어차피 같은 효과가 적용되니까

상위 요소에 이벤트 핸들러를 한 번 등록하면 된다. 이것이 이벤트 위임이다.

<!DOCTYPE html>
<html>
<head>
  <style>
    #fruits {
      display: flex;
      list-style-type: none;
      padding: 0;
    }

    #fruits li {
      width: 100px;
      cursor: pointer;
    }

    #fruits .active {
      color: red;
      text-decoration: underline;
    }
  </style>
</head>
<body>
  <nav>
    <ul id="fruits">
      <li id="apple" class="active">Apple</li>
      <li id="banana">Banana</li>
      <li id="orange">Orange</li>
    </ul>
  </nav>
  <div>선택된 내비게이션 아이템: <em class="msg">apple</em></div>
  <script>
    const $fruits = document.getElementById('fruits');
    const $msg = document.querySelector('.msg');

    // 사용자 클릭에 의해 선택된 내비게이션 아이템(li 요소)에 active 클래스를 추가하고
    // 그 외의 모든 내비게이션 아이템의 active 클래스를 제거한다.
    function activate({ target }) {
      // 이벤트를 발생시킨 요소(target)가 ul#fruits의 자식 요소가 아니라면 무시한다.
      if (!target.matches('#fruits > li')) return;

      [...$fruits.children].forEach($fruit => {
        $fruit.classList.toggle('active', $fruit === target);
        $msg.textContent = target.id;
      });
    }

    // 이벤트 위임: 상위 요소(ul#fruits)는 하위 요소의 이벤트를 캐치할 수 있다.
    $fruits.onclick = activate;
  </script>
</body>
</html>

여기서 주의할 점은 이벤트가 실제로 발생한 위치 즉, target과 이벤트 핸들러를 걸어논 곳 currentTarget이 다를 수 있다.

바나나를 클릭했으면 이벤트가 발생한 타겟은 li 태그지만 이벤트 핸들러가 걸려있는 곳은 상위 요소인 ul 태그이다.

이벤트 위임으로 얻을 수 있는 장점을 정리하면 다음과 같다.

  • 동적으로 엘리먼트를 추가할 때마다 핸들러를 고려할 필요가 없다.
  • 상위 엘리먼트에 하나의 이벤트 핸들러만 추가하면 되기 때문에 코드가 훨씬 깔끔해진다.
  • 메모리에 있게되는 이벤트 핸들러가 적어지기 때문에 퍼포먼스 측면에서 이점이 있다.

참고 자료

https://github.com/baeharam/Must-Know-About-Frontend/blob/main/Notes/javascript/event-delegation.md

 

'자바스크립트' 카테고리의 다른 글

호이스팅  (0) 2024.03.06
스코프  (0) 2024.03.03
Ajax  (0) 2024.03.01
클로저 원리  (0) 2024.02.16
실행 컨텍스트와 렉시컬 환경 심화 탐구  (0) 2024.02.09