개발자 Saaad

[JavaScript] 이벤트 위임 (Event Delegation) 본문

학습/kakao X goorm 풀스택12회차

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

Saaad 2024. 11. 27. 19:39

Event Delegation

캡처링과 버블링을 활용하면 강력한 이벤트 핸들링 패턴인 이벤트 위임(event delegation) 을 구현 할 수 있습니다.

이벤트 위임은 비슷한 방식으로 여러 요소를 다뤄야 할 때 사용됩니다.

이벤트 위임을 사용하면 요소마다 핸들러를 할당하지 않고, 요소의 공통 조상에 이벤트 핸들러를 단 하나만 할당해도 

여러 요소를 한꺼번에 다룰 수 있습니다.

즉, 이벤트 위임은 '하위 요소의 이벤트를 상위 요소에 위임하는 것' 입니다.

 

즉, 하위 요소의 이벤트를 상위에서 제어합니다.

 


예제로 살펴보기

다음은 고대 중국 철학에서 유래한 팔괘도(Ba-Gua diagram) 입니다.

 

HTML은 대략 다음과 같습니다.

<table>
  <tr>
    <th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
  </tr>
  <tr>
    <td class="nw"><strong>Northwest</strong><br>Metal<br>Silver<br>Elders</td>
    <td class="n">...</td>
    <td class="ne">...</td>
  </tr>
  <tr>...2 more lines of this kind...</tr>
  <tr>...2 more lines of this kind...</tr>
</table>

 

지금 보는 표에는 9개의 칸이 있습니다. 하지만 칸이 99개이든 9,999개이든 상관없습니다.

지금 해야 할 작업은 <td>를 클릭했을 때, 그 칸을 강조하는 것입니다.

각 <td>마다 onclick 핸들러를 할당하는 대신, ‘모든 이벤트를 잡아내는’ 핸들러를 <table> 요소에 할당해 보겠습니다.

<table> 요소에 할당하게 될 핸들러는 event.target을 이용해 어떤 요소가 클릭 되었는지 감지하고, 해당 칸을 강조하게 됩니다.

코드는 아래와 같습니다.

let selectedTd;

table.onclick = function(event) {
  let target = event.target; // 클릭이 어디서 발생했을까요?

  if (target.tagName != 'TD') return; // TD에서 발생한 게 아니라면 아무 작업도 하지 않습니다,

  highlight(target); // 강조 함
};

function highlight(td) {
  if (selectedTd) { // 이미 강조되어있는 칸이 있다면 원상태로 바꿔줌
    selectedTd.classList.remove('highlight');
  }
  selectedTd = td;
  selectedTd.classList.add('highlight'); // 새로운 td를 강조 함
}

 

이렇게 코드를 작성하면 테이블 내 칸의 개수는 고민거리가 되지 않습니다. 강조기능을 유지하면서 <td>를 언제라도 넣고 뺄 수 있게 됩니다.

하지만 단점도 있습니다.

위와 같이 구현하면 클릭 이벤트가 <td>가 아닌 <td> 안에서 동작할 수 있습니다.

팔괘도의 HTML 살펴봅시다. <td>안에 중첩 태그 <strong> 있는 것을 확인할 있습니다.

<td>
  <strong>Northwest</strong>
  ...
</td>

 

<strong> 클릭하면 event.target <strong> 해당하는 요소가 저장됩니다.

따라서 table.onclick 핸들러에서 event.target을 이용해 클릭 이벤트가 <td>안쪽에서 일어났는지 아닌지를 알아내야 합니다.

이러한 단점을 반영하여 기능을 향상한 코드는 다음과 같습니다.

table.onclick = function(event) {
  let td = event.target.closest('td'); // (1)

  if (!td) return; // (2)

  if (!table.contains(td)) return; // (3)

  highlight(td); // (4)
};

 

설명

  1. elem.closest(selector) 메서드는 elem 상위 요소  selector 일치하는 가장 근접한 조상 요소를 반환합니다.
    코드에선 이벤트가 발생한 요소부터 시작해 위로 올라가며 가장 가까운 <td> 요소를 찾습니다.
  2. event.target <td>안에 있지 않으면 즉시 null 반환하므로 아무 작업도 일어나지 않습니다.
  3. 중첩 테이블이 있는 경우 event.target 현재 테이블 바깥에 있는 <td> 수도 있습니다. 이런 경우를 처리하기 위해 
    <td> 팔괘도 안에 있는지를 확인합니다.
  4. 이제 진짜 td 강조해 줍니다.

 


결론 

이벤트 위임은 상당히 멋진 패턴입니다. DOM 이벤트에 적용할 수 있는 아주 유용한 패턴이죠,

이벤트 위임은 유사한 요소에 동일한 핸들러를 적용할 때 주로 사용하지만 꼭 이런 경우에만 사용할 수 있는 것은 아닙니다.

이벤트 위임은 다음과 같은 알고리즘으로 동작합니다.

 

1. 컨테이너에 하나의 핸들러를 할당합니다.

2. 핸들러의 event.target 을 사용해 이벤트가 발생한 요소가 어디인지 알아냅니다.

3. 원하는 요소에서 이벤트가 발생했다고 확인되면 이벤트를 핸들링합니다.

 

장점

  • 많은 핸들러를 할당하지 않아도 되기 때문에 초기화가 단순해지고 메모리가 절약됩니다.
  • 요소를 추가하거나 제거할 때 해당 요소에 할당된 핸들러를 추가하거나 제거할 필요가 없기 때문에 코드가 짧아집니다.
  • innerHTML이나 유사한 기능을 하는 스크립트로 요소 덩어리를 더하거나 있기 때문에 DOM 수정이 쉬워집니다.

단점

  • 이벤트 위임을 사용하려면 이벤트가 반드시 버블링 되어야 합니다. 하지만 몇몇 이벤트는 버블링 되지 않습니다. 그리고 낮은 레벨에 할당한 핸들러엔 event.stopPropagation()를 쓸 수 없습니다.
  • 컨테이너 수준에 할당된 핸들러가 응답할 필요가 있는 이벤트이든 아니든 상관없이 모든 하위 컨테이너에서 발생하는 이벤트에 응답해야 하므로 CPU 작업 부하가 늘어날 있습니다. 그런데 이런 부하는 무시할만한 수준이므로 실제로는 고려하지 않습니다.

 

 

하나 배운점 

HTML의 tagName 속성은 항상 대문자로 반환된다! 따라서 태그이름을 비교할 때는 대소문자를 구분하지 않고 동작한다!

  •