이벤트 위임 (event delegation)
이벤트 위임
이벤트 위임은 캡쳐링과 버블링을 이용한 것으로, 여러 엘리먼트마다 각각 이벤트 핸들러를 할당하지 않고, 공통되는 부모에 이벤트 핸들러를 할당하여 이벤트를 관리하는 방식이다.
1) 여러 개의 자식 엘리먼트 이벤트를 한 번에 관리
: HTML의 읽기전용 속성인 데이터 속성을 사용하여 이벤트 위임을 구현할 수 있다.
데이터 속성
여기서 데이터 속성은 화면에 안 보이게 글이나 추가 정보를 엘리먼트에 담을 수 있는 속성으로 data- 로 시작하는 속성이다.
<article
id="electriccars"
data-columns="3"
data-index-number="12314"
data-parent="cars">
...
</article>
위처럼 html을 작성하면 각각의 데이터 속성을 읽는 방법은 아래와 같다.
(대시는 camelCase로 변환된다.)
var article = document.getElementById('electriccars');
article.dataset.columns // "3"
article.dataset.indexNumber // "12314"
article.dataset.parent // "cars"
속성을 바꾸기 위해서는 article.dataset.columns = 5 와 같이 작성해주면 된다.
데이터 속성은 Html속성이기 때문에 CSS에서도 접근이 가능한데, 위의 예시에서 data-parent를 article에 보여주기 위해서는 아래와 같이 작성하면 된다.
article::before {
content: attr(data-parent);
}
이때 아래와 같이 attribute selector를 사용하여 데이터에 따른 스타일을 변경해 줄 수 있다.
article[data-columns='3'] {
width: 400px;
}
article[data-columns='4'] {
width: 600px;
}
이를 이용하여 서로 다른 역할을 하는 세 개의 버튼의 이벤트를 어떻게 이벤트 위임 방식으로 처리하는지 살펴보자.
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example</title>
</head>
<body>
<div id="Menu">
<button data-action="save">저장하기</button>
<button data-action="reset">초기화 하기</button>
<button data-action="load">불러오기</button>
</div>
<script src="app.js"></script>
</body>
</html>
const $Menu = document.getElementById('Menu')
const ActionFunctions = {
save: () => alert('저장하기'),
reset: () => alert('초기화하기'),
load: () => alert('불러오기'),
}
$Menu.addEventListener('click', e => {
const action = e.target.dataset.action
if (action) {
ActionFunctions[action]()
}
})
2) 동적 엘리먼츠에 대한 이벤트 관리하기
: 동적으로 추가되거나 삭제하는 엘리먼트에 매번 이벤트 리스너를 추가하고 삭제한다면 코드의 효율성을 낮추고 리스터가 제대로 삭제되지 않는 경우 메모리 누수의 가능성도 커진다. 이때 이벤트 위임의 방식을 사용하여 더 나은 코드를 작성할 수 있다.
아래의 예는 itemList에 두가지 항목을 넣고 자바스크립트 코드에서 각각의 input에 click이벤트 리스너를 달아주는 코드이다.
<h1>오늘의 할 일</h1>
<ul class="itemList">
<li>
<input type="checkbox" id="item1">
<label for="item1">이벤트 버블링 학습</label>
</li>
<li>
<input type="checkbox" id="item2">
<label for="item2">이벤트 캡쳐 학습</label>
</li>
</ul>
var inputs = document.querySelectorAll('input');
inputs.forEach(function(input) {
input.addEventListener('click', function(event) {
alert('clicked');
});
});
이때 이상태에서 자바스크립트 코드로 아이템을 아래와 같이 추가하게 된다면 문제가 발생한다.
// 새 리스트 아이템을 추가하는 코드
var itemList = document.querySelector('.itemList');
var li = document.createElement('li');
var input = document.createElement('input');
var label = document.createElement('label');
var labelText = document.createTextNode('이벤트 위임 학습');
input.setAttribute('type', 'checkbox');
input.setAttribute('id', 'item3');
label.setAttribute('for', 'item3');
label.appendChild(labelText);
li.appendChild(input);
li.appendChild(label);
itemList.appendChild(li);
새로 추가된 아이템에도 이벤트 리스너가 등록되길 원했지만 이벤트 리스너가 등록되는 시점에는 리스트 아이템이 기존의 2개만 존재하기 때문에 추가된 아이템에는 등록되지 않음을 확인할 수 있다.
이때 각각의 아이템이 추가될때마다 이벤트 리스너를 달아주는 것보다 상위 요소(예시에서는 itemList)에 이벤트 리스너를 추가해줌으로써 하위 요소의 이벤트를 감지하게 하는 방법이 좋다.
var itemList = document.querySelector('.itemList');
itemList.addEventListener('click', function(event) {
alert('clicked');
});
정리
이벤트 위임은 다음과 같은 알고리즘으로 동작한다.
- 컨테이너에 하나의 핸들러를 할당합니다.
- 핸들러의 event.target을 사용해 이벤트가 발생한 요소가 어디인지 알아냅니다.
- 원하는 요소에서 이벤트가 발생했다고 확인되면 이벤트를 핸들링합니다.
- 장점
- 많은 핸들러를 할당하지 않아도 되기 때문에 초기화가 단순해지고 메모리가 절약됩니다.
- 요소를 추가하거나 제거할 때 해당 요소에 할당된 핸들러를 추가하거나 제거할 필요가 없기 때문에 코드가 짧아집니다.
- innerHTML이나 유사한 기능을 하는 스크립트로 요소 덩어리를 더하거나 뺄 수 있기 때문에 DOM 수정이 쉬워집니다.
- 단점
- 이벤트 위임을 사용하려면 이벤트가 반드시 버블링 되어야 합니다. 하지만 몇몇 이벤트는 버블링 되지 않습니다. 그리고 낮은 레벨에 할당한 핸들러엔 event.stopPropagation()를 쓸 수 없습니다.
- 컨테이너 수준에 할당된 핸들러가 응답할 필요가 있는 이벤트이든 아니든 상관없이 모든 하위 컨테이너에서 발생하는 이벤트에 응답해야 하므로 CPU 작업 부하가 늘어날 수 있습니다. 그런데 이런 부하는 무시할만한 수준이므로 실제로는 잘 고려하지 않습니다.
--- 참고 사이트
- 이벤트 위임
- [JavaScript] 이벤트 버블링, 캡쳐링, 위임