Front-End/JavaScript

자바스크립트[JavaScript] | 이벤트 위임(Event Delegation)으로 문제 해결하기

jaeyeong 2023. 4. 12. 16:29

포스팅하게 된 계기

노마드 코더에서 진행하는 멜론 클론코딩 컨테스트에 참여하여 바닐라 JS로 코딩을 하던 중,

버튼을 클릭했을 때 원하는 정보를 가져오는 코드를 작성하던 중에 문제가 발생했다.

이벤트 버블링을 활용한 개념인 이벤트 위임을 이용하여 문제를 해결했고,

내용을 기억하기 위해 정리를 하기로 했다.

 

문제

음악 차트의 재생 버튼 부분을 구현하던 중이었고 button안에 svg 파일을 추가해 놓았다.

음악의 재생 버튼을 클릭했을 때 노래 제목을 가져와 서버에 보내면

해당 노래의 Youtube 영상을 반환하는 코드를 작성하고 있었다.

노래를 구성하고 있는 li태그의 id에 노래 제목을 적어두었다.

<ul id="song-list">
    <li id="노래제목" class="song-노래인덱스">
        ...
        <button id="song-노래인덱스 play">
          <svg>
            <!-- SVG 내부 요소 -->
          </svg>
        </button>
        ...
    </li>
</ul>

 

내가 원하는 동작은 button을 클릭하면 클릭된 노래의 제목 또는 정보를 가져오는 것이었다.

하지만 button을 찾아 addEventListener를 연결하고 event 객체를 출력하면 svg 정보만 출력되었다.

const button = document.getElementsByClassName('play');
button.addEventListener('click', (el) => console.log(el)); // svg 내부 속성 출력
button.addEventListener('click', (el) => console.log(el.target)); // svg 내부 속성 출력

 

이 문제를 해결하기 위해 적용했던 이벤트 위임을 알아보기 전에 이벤트 버블링에 대해 먼저 알아보도록 하자.

 

이벤트 버블링이란?

자식 요소에서 발생한 이벤트부모 요소로 전파되는 방식이벤트 버블링(Event Bubbling)이라고 한다.

웹 페이지는 DOM(Document Object Model) 트리 구조로 구성되어 있으며, 이 구조를 따라 이벤트가 전파된다.

이벤트 버블링은 자식 요소에서 발생한 이벤트가 부모 요소로 전파되면서 차례로 이벤트 핸들러를 실행한다.

 

실행된 이벤트 버블링이 종료되는 때는 다음과 같다.

  1. 이벤트가 최상위 요소인 document 객체에 도달하면 버블링이 종료됨
  2. 이벤트 객체의 stopPropagation() 메서드를 사용하여 이벤트 버블링을 중단함

어떻게 동작하는지 한번 코드를 보며 이해해 보자.

<div id="container">
  <button id="button">Click me!</button>
</div>

 

HTML 코드가 위와 같이 작성되어 있을 때

'button' id를 가진 button 태그와 'container id'를 가진 div 태그에 addEventListener를 이용해서

클릭했을 때 콘솔에 문구가 출력되도록 하기 위한 코드는 다음과 같다.

const container = document.getElementById('container');
const button = document.getElementById('button');

container.addEventListener('click', () => {
  console.log('Container clicked');
});

button.addEventListener('click', (event) => {
  console.log('Button clicked');
});

 

이벤트 버블링이 발생해서 사진과 같이 button 이벤트 함수가 실행된 다음 div 이벤트 함수가 실행된다.

내가 클릭한 건 button 태그인데 div 태그까지 이벤트 함수가 실행되는 것을 보니 살짝 신기했다.

그리고 이벤트를 잘 다루지 못하면 불필요한 이벤트가 발생할 수도 있겠다는 생각이 들었다.

 

이제 이벤트 버블링을 차단하여 button 이벤트만 발생하도록 수정해 보자.

const container = document.getElementById('container');
const button = document.getElementById('button');

container.addEventListener('click', () => {
  console.log('Container clicked');
});

button.addEventListener('click', (event) => {
  console.log('Button clicked');
  event.stopPropagation(); // 이벤트 버블링 중단
});

 

 

이렇게 stopPropagation()를 이용하여 이벤트 버블링을 차단할 수 있으며

div 이벤트 함수는 실행이 되지 않는 것을 확인할 수 있다.

이벤트 위임이란?

이벤트 버블링을 활용하여 하위 요소들의 이벤트상위 요소에서 일괄 처리하는 것이벤트 위임(Event Delegation)이라고 한다.

나는 인자로 전달된 선택자와 일치하는 가장 가까운 조상 요소를 찾아주는

event.target.closest() 메서드를 사용하여 문제를 해결했다.

const play = document.createElement('button');

play.onclick = (event) => {
  const li = event.target.closest('li');
  window.location.href = `/player?title=${encodeURIComponent(li.id)}`;
};

 

포스팅을 시작할 때 작성한 HTML 코드에 있는 li 태그는 여러 개이며, 자바스크립트로 요소를 동적으로 생성하고 있다.

위 코드는 동적으로 생성하는 코드의 일부인데, 버튼을 생성하면서 onclick 이벤트를 추가하였다.

 

버튼이 클릭되면, closest() 메서드를 사용하여 해당 버튼과 가장 가까운 li 태그(클릭한 노래의 정보가 담긴 태그)를 찾고,

li의 id인 노래 제목을 가져와 player로 노래를 재생시킨다.

 

하지만 이는 좋은 해결책은 아니다.

 

왜냐하면 이벤트 위임의 주요 목적은 상위 요소에 이벤트 핸들러를 추가해서 하위 요소들의 이벤트를 일괄적으로 처리하고,

이를 통해 코드가 간단해지고 효율성을 높여주는데,

내가 작성한 코드는 각각의 자식 요소에서 이벤트를 핸들링하고 있고,

이는 각 요소에 이벤트 리스너가 추가되기 때문에 이벤트 위임의 장점이 사라진다.

 

그래서 작성한 코드를 리팩터링 해보았다.

songList.addEventListener('click', (event) => {
  const playButton = event.target.closest('button');

  if (playButton) {
    const li = playButton.closest('li');
    window.location.href = `/player?title=${encodeURIComponent(li.id)}`;
  }
});

 

li 태그를 감싸고 있는 ul 태그(= songList, 상위 요소)를 찾아 해당 태그에 이벤트 핸들링을 하도록 수정했다.

songList에 클릭 이벤트가 발생하면 클릭된 이벤트 대상이 button 태그인지 확인하고,

맞다면 이벤트 대상인 버튼에 인접한 li 태그를 찾아 노래 정보를 쿼리 스트링에 담아 이동시킨다.

 

이렇게 수정함으로써 하위 모든 button 태그에 이벤트를 할당하지 않고,

상위 요소에서 이벤트 핸들링을 하여 일괄적으로 이벤트를 관리할 수 있게 되었다!

 

다음엔 이벤트 캡쳐링에 대해 한번 공부해봐야겠다.