출처 : 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)
};
- elem.closest(selector) 는 가장 가까운 ancestor를 반환한다.
- td안에 없으면 return
- event.target이 td여도 current table 밖이면 return
- 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을 사용할 수 있다.
- element에 행동에 대해 서술하는 custom attribute를 추가한다.
- 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을 추가하는데 사용한다.
알고리즘:
- container에 하나의 handler를 추가한다.
- handler에서 source element의 event.target을 체크한다.
- 해당 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는 무시할 만한 수준이다.
'JavaScript' 카테고리의 다른 글
JavaScript] 장시간 움직임이 없는 경우 로그아웃 처리 (0) | 2023.08.30 |
---|---|
Web APIs - Console API (0) | 2022.12.05 |
JavaScript] Event Bubbling and capturing (0) | 2022.11.23 |
JavaScript] DOM events, Event handler, addEventListener (0) | 2022.11.22 |
JavaScript] Coordinates (element 좌표) (0) | 2022.11.17 |
댓글