본문 바로가기
JavaScript

JavaScript] Event delegation

by Fastlane 2022. 12. 9.
728x90
반응형

출처 : https://javascript.info/event-delegation

Capturing, bubbling은 강력한 event handling 패턴인 event delegation의 구현을 가능하게 해준다. 

많은 elements를 비슷한 방식으로 handle할 때, 각각 handler를 할당하는 것이 아닌 공통 ancestor에만 하나의 handler를 할당할 수 있다. 

 

handler에서 우리는 event.target으로 event가 실제로 어디서 발생하는지 알 수 있다.

Ba-Gua diagram을 예로 들어보자. 

 HTML은 다음과 같다. 

<table id="bagua-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 또는 9999 든 상관이 없다. 

<td> 칸 클릭 시, highlight를 주어보자. 

 

모든 <td>에 onclick handler를 할당하는 대신, <table>에 handler를 할당해보자. 

event.target을 사용해서 클릭된 element를 찾아, highlight를 준다. 

let table = document.getElementById('bagua-table');
let selectedTd;

table.onclick = function(event) {
  let target = event.target; // where was the click?

  if (target.tagName != 'TD') return; // not on TD? Then we're not interested

  highlight(target); // highlight it
};

function highlight(td) {
  if (selectedTd) { // remove the existing highlight if any
    selectedTd.classList.remove('highlight');
  }
  selectedTd = td;
  selectedTd.classList.add('highlight'); // highlight the new td
}

하지만, 결점이 있다. <td>내부의 <strong>같은 태그 내부가 클릭된 경우, target.tagName은 'STRONG'이 되어 highlight 함수가 호출되지 않는다. 

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

따라서 event.target을 찾아서 element가 <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) 는 가장 가까운 ancestor를 반환한다. 
  2. td안에 없으면 return 
  3. event.target이 td여도 current table 밖이면 return 
  4. highlight 처리 

Delegation example: actions in markup

event delegation의 또 다른 사용법이 있다. 

'Save', 'Load', 'Search' 등의 버튼이 있는 메뉴를 만들고, save, load, search 함수를 가진 object가 있을때 어떻게 서로 match를 할 수 있을까?

 

첫번째 생각은, 각 버튼에 별개의 handler를 할당하는 것이다. 하지만 더 나은 방법이 있따. 모든 menu에 대한 하나의 handler를 할당하고, 함수호출하는 버튼에 data-action attribute를 추가하는 것이다. 

 

<div id="menu">
  <button data-action="save">Save</button>
  <button data-action="load">Load</button>
  <button data-action="search">Search</button>
</div>
  class Menu {
    constructor(elem) {
      elem.onclick = this.onClick.bind(this); 
    }

    save() {
      alert('saving');
    }

    load() {
      alert('loading');
    }

    search() {
      alert('searching');
    }

    onClick(event) {    
      let action = event.target.dataset.action;
      if (action) {
        this[action]();
      }
    };
  }
  
  new Menu(document.getElementById('menu'));

The “behavior” pattern

특별한 attributes, classes와 함께 elements의 행동에 대해서 서술하기 위해 event delegation을 사용할 수 있다. 

  1. element에 행동에 대해 서술하는 custom attribute를 추가한다. 
  2. document-wide handler는 event를 track하고, attributed element에 event가 발생하면 동작을 실행한다. 

Behavior: Counter

예를들어, data-counter attribute는 button에 '클릭 시, 값 증가'라는 동작을 추가한다. 

Counter: <input type="button" value="1" data-counter>
One more counter: <input type="button" value="2" data-counter>

<script>
  document.addEventListener('click', function(event) {

    if (event.target.dataset.counter != undefined) { // if the attribute exists...
      event.target.value++;
    }

  });
</script>

button 클릭 시, value가 증가한다.event delegation을 사용하여 HTML을 확장할 수 있다. 

  • document-level handler는 addEventListener를 사용해야 한다. document.on<event>는 새로운 handler가 기존 handler를 overwrite하므로 사용하지 않는다. 

Behavior: Toggler

behavior예를 하나 더 들어보자. data-toggle-id attribute가 있는 element를 클릭 시, 주어진 id와 함께 element가 show/hide한다. 

<button data-toggle-id="subscribe-mail">
  Show the subscription form
</button>

<form id="subscribe-mail" hidden>
  Your mail: <input type="email">
</form>

<script>
  document.addEventListener('click', function(event) {
    let id = event.target.dataset.toggleId;
    if (!id) return;

    let elem = document.getElementById(id);

    elem.hidden = !elem.hidden;
  });
</script>

"behavior" 패턴은 자바스크립트의 mini-fragment를 대체하여 사용할 수 있다. 

 

Summary

Event delegation은 DOM event를 위한 가장 도움이 되는 패턴이다. 

많은 수의 비슷한 elements에 동일한 handling을 추가하는데 사용한다. 

알고리즘:

  1. container에 하나의 handler를 추가한다. 
  2. handler에서 source element의 event.target을 체크한다. 
  3. 해당 element에 원하는 동작이 있으면 event를 handle한다. 

Benefits:

  • 단순한 초기화, 메모리 절약
  • 적은양의 코드, element의 추가/삭제에 따라 handler 추가/삭제가 필요없다. 
  • DOM 수정 : 많은 수의 elements를 추가 또는 삭제할 수 있다. 

delegation이 가진 한계

  • event는 bubbling하지만, 어떤 event는 bubble하지 않는다. 또한 low-level handler에서는 event.stopPropagation()을 사용하면 안된다.
  • delegation은 CPU 로드를 추가할 수 있다. container-level handler는 container의 어느곳의 event라도 반응하기 때문이다. 하지만 보통 load는 무시할 만한 수준이다. 

 

728x90
반응형

댓글