이벤트 등록은 웹 애플리케이션에서 사용자의 입력을 받기 위해서 필요한 기능이다.
<button>add Item</button>
let addItem = event => console.log(event);
let button = document.querySelector('button');
button.addEventListener('click', addItem);
위 예제는 add Item
버튼을 클릭하면 addItem 함수가 실행되어 event 파라미터를 출력하여 이벤트에 대한 정보를 확인할 수 있다.
addEventListener()
Web API는 개발자들이 화면에 동적인 기능을 추가하기 위해서 자연스럽게 접하게 되는 기능이다. 브라우저가 어떻게 이벤트를 감지하는지 2가지 방식에 대해서 알아보자.
상위 화면 요소
HTML은 트리 구조를 가지고 있으며 트리 구조상 한 단계 위에 있는 요소를 상위 요소라고 부른다.
이벤트 버블링은 특정 화면 요소에서 이벤트가 발생했을 때 상위 화면 요소들로 전달
되어 가는 특성이다.
위 그림은 아래의 예시를 도식화한 그림이다.
<body>
<div class="one">
<div class="two">
<div class="three">
</div>
</div>
</div>
</body>
let divs = document.querySelectorAll('div');
let logEvent = event => console.log(event.currentTarget.className);
divs.forEach(div => {
div.addEventListener('click', logEvent);
})
<div class="three"/>
를 클릭하면 아래와 같은 결과가 나온다.
<div class="three"/>
만 클릭했을 뿐인데 3개의 이벤트가 발생한 이유는 브라우저가 이벤트를 감지하는 방식때문이다.
브라우저는 이벤트가 발생했을 때 이벤트를 최상위에 있는 화면 요소까지 이벤트를 전파한다.
하위 요소에서 상위 요소로 이벤트가 전파되는 방식을 이벤트 버블링
이라고 부른다.
이벤트 캡처링은 이벤트 버블링과 반대 방향으로 이벤트가 전파된다.
이벤트가 발생했을 때 최상위 요소인 body 태그에서 해당 태그를 찾아 내려간다.
코드로는 이벤트 버블링 코드와 대부분 동일하고 addEventListener() API 옵션 객체에 capture: true를 설정해주면 이벤트를 감지하기 위해 이벤트 버블링과 반대 방향으로 탐색한다.
<body>
<div class="one">
<div class="two">
<div class="three">
</div>
</div>
</div>
</body>
let divs = document.querySelectorAll('div');
let logEvent = event => console.log(event.currentTarget.className);
divs.forEach(div => {
div.addEventListener('click', logEvent, {
capture: true // default: false
});
})
를 클릭하면 아래와 같은 결과가 나오는걸 볼 수 있다.
event.stopPropagation()
Web API를 사용하면 이벤트가 전파되는 것을 막을 수 있다.
이벤트 버블링의 경우에는 클릭한 요소의 이벤트만 발생시키고 상위 요소로 이벤트를 전달하는 것을 막을 수 있다.
이벤트 캡처링의 경우에는 클릭한 요소의 최상위 요소의 이벤트만 동작시키고 하위 요소들로 이벤트를 전달하지 않는다.
// 이벤트 버블링 예제
let divs = document.querySelectorAll('div');
let logEvent = (event) => {
event.stopPropagation();
console.log(event.currentTarget.className);
}
divs.forEach(div => {
div.addEventListener('click', logEvent);
});
// 이벤트 캡처링 예제
let divs = document.querySelectorAll('div');
let logEvent = (event) => {
event.stopPropagation();
console.log(event.currentTarget.className);
}
divs.forEach(div => {
div.addEventListener('click', logEvent, {
capture: true
});
});
이벤트 버블링과 캡처링은 이벤트 위임을 위한 선수 지식이다. 바닐라 JS로 웹 앱을 구현할 때 자주 사용되는 패턴이다.
이벤트 위임은 하위 요소들에 각각 이벤트를 붙이지 않고 상위 요소에서 하위 요소의 이벤트를 제어하는 방식
이다.
<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>
let inputs = document.querySelectorAll('input');
inputs.forEach((input) => {
input.addEventListener('click', () => {
alert('clicked');
})
});
위 예제 코드를 실행하면 문제없이 잘 동작한다.
만약 여기서 할 일이 더 생겨 리스트 아이템을 추가하면 클릭 이벤트가 정상적으로 동작하지 않는다.
// 리스트 아이템을 추가하는 코드
let itemList = document.querySelector('.itemList');
let li = document.createElement('li');
let input = document.createElement('input');
let label = document.createElement('label');
let labelText = document.createTextNode('이벤트 위임 학습');
input.setAttrubute('type', checkbox);
input.setAttribute('id', 'item3');
label.setAttribute('for', 'item3');
label.appendChild(labelText);
li.appendChild(input);
li.appendChild(label);
itemList.appendChild(li);
이유는 새롭게 추가된 아이탬에는 클릭 이벤트 리스너가 등록되지 않았다.
새롭게 아이템이 추가될 때마다 이벤트 리스너를 일일이 달아주면 작업이 매우 번거롭고 모든 아이템에 이벤트 리스너가 등록되면 메모리도 많이 잡아먹을 수 있다.
위에 언급한 문제들은 이벤트 위임으로 해결할 수 있다.
// 모든 아이템에 이벤트 리스너를 등록하는 코드 주석 처리
// let inputs = document.querySelectorAll('input');
// inputs.forEach((input) => {
// input.addEventListener('click', () => {
// alert('clicked');
// })
// });
// 상위 요소인 ul 태그에 이벤트 리스너를 등록
let itemList = document.querySelector('.itemList;
itemList.addEventListener('click', event => {
alert('click');
});
상위 요소인 ul 태그에 이벤트 리스너를 등록하고 하위에서 발생한 이벤트 클릭 이벤트를 감지할 수 있기 때문에 아이템이 추가 될 때마다 이벤트를 등록할 필요가 없어진다.
참고
https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/