이벤트 버블링, 캡쳐, 위임

늘보·2021년 8월 26일
1

Web

목록 보기
3/6
post-custom-banner

이 글은 'CAPTAIN PANGYO`님의 '이벤트 버블링, 이벤트 캡처 그리고 이벤트 위임까지' 포스팅을 이해하며 다시 정리한 글입니다. 문제가 될 시 삭제하도록 하겠습니다.

참조 링크: https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/

이벤트 버블링(Event Bubbling)

  • 이벤트 버블링은 특정 화면 요소에서 이벤트가 발생했을 때 해당 이벤트가 더 상위의 화면 요소들로 전달되어 가는 특성을 의미한다.

상위의 화면 요소란? HTML 요소는 기본적으로 트리 구조를 갖습니다. 여기서는 트리 구조상으로 한 단계 위에 있는 요소를 상위 요소라고 하며 body 태그를 최상위 요소라고 부를 예정이다.

<body>
	<div class="one">
		<div class="two">
			<div class="three">
			</div>
		</div>
	</div>
</body>
var divs = document.querySelectorAll('div');
divs.forEach(function(div) {
	div.addEventListener('click', logEvent);
});

function logEvent(event) {
	console.log(event.currentTarget.className);
}
  • 위 코드는 세 개의 div 태그에 모두 클릭 이벤트를 등록하고 클릭 했을 때 logEvent 함수를 실행시키는 코드다. 여기서 위 그림대로 최하위 div 태그 <div class="three"></div>를 클릭하면 아래와 같은 결과가 실행된다.

분명 div 태그 하나만 클릭했는데 3개의 이벤트가 발생했다. 그 이유는 뭘까?

  • 이는 브라우저가 특정 화면 요소에서 이벤트가 발생했을 때 그 이벤트를 최상위에 있는 화면 요소까지 이벤트를 전파시키기 때문이다.

  • 이와 같이 하위에서 상위 요소로의 이벤트 전파 방식을 이벤트 버블링(Event Bubbling)이라고 한다.

이벤트 캡쳐(Event Capture)

  • 이벤트 캡쳐는 이벤트 버블링과 반대 방향으로 진행되는 이벤트 전파 방식이다.

그림과 같이 특정 이벤트가 발생했을 때 최상위 요소인 body 태그에서 이벤트가 발생한 태그를 찾아내려 한다. 어떻게 구현할 수 있을까?

<body>
	<div class="one">
		<div class="two">
			<div class="three">
			</div>
		</div>
	</div>
</body>
var divs = document.querySelectorAll('div');
divs.forEach(function(div) {
	div.addEventListener('click', logEvent, {
		capture: true // default 값은 false입니다.
	});
});

function logEvent(event) {
	console.log(event.currentTarget.className);
}
  • addEventListener() API에서 옵션 객체에 capture:true를 설정해주면 된다. 그러면 해당 이벤트를 감지하기 위해 이벤트 버블링과 반대 방향으로 탐색합니다.

  • 따라서, 아까와 동일하게 <div class="three"></div>를 클릭하면 아래와 같이 거꾸로 결과가 나타나게 된다.

event.stopPropagation()

만약 원하는 화면 요소의 이벤트만 신경을 쓰고 싶다면 어떻게 해야할까?

  • 그럴 때 사용하는게 stopPropagation() 웹 API이다.
function logEvent(event) {
	event.stopPropagation();
}
  • 위 API는 해당 이벤트가 전파되는 것을 막는다. 따라서, 이벤트 버블링의 경우에는 클릭한 요소의 이벤트만 발생시키고 상위 요소로 이벤트를 전달하는 것을 방해한다. 그리고 이벤트 캡쳐의 경우에는 클릭한 요소의 최상위 요소의 이벤트만 동작시키고 하위 요소들로 이벤트를 전달하지 않게 된다.
// 이벤트 버블링 예제
divs.forEach(function(div) {
	div.addEventListener('click', logEvent);
});

function logEvent(event) {
	event.stopPropagation();
	console.log(event.currentTarget.className); // three
}
  • 콘솔에 three만 찍히는 것을 볼 수 있다. (이벤트가 상위로 전파되지 않음)
// 이벤트 캡쳐 예제
divs.forEach(function(div) {
	div.addEventListener('click', logEvent, {
		capture: true // default 값은 false입니다.
	});
});

function logEvent(event) {
	event.stopPropagation();
	console.log(event.currentTarget.className); // one
}
  • 마찬가지로 이벤트가 하위로 전파되지 않는다. 콘솔에 one만 찍힌다.

이벤트 위임(Event Delegation)

  • 해당 포스팅을 작성하신 분의 말에 따르면, 앞서 살펴본 이벤트 버블링과 캡쳐는 사실 이벤트 위임을 위한 선수 지식이라고 한다. 이벤트 위임은 실제 Vanilla JS로 웹 앱을 구현할 때 자주 사용하게 되는 코딩 패턴이다.

이벤트 위임이란, '하위 요소에 각각 이벤트를 붙이지 않고 상위 요소에서 하위 요소의 이벤트들을 제어하는 방식'을 의미한다.

<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);
  • 새로 추가된 리스트 아이템(이벤트 위임 학습)에서 클릭 이벤트가 동작하지 않게 된다.

  • 코드를 보면, 인풋 박스에 클릭 이벤트 리스너를 추가하는 시점에서 리스트 아이템은 두 개다. 따라서, 새롭게 추가된 리스트 아이템에는 클릭 이벤트 리스너가 등록되지 않았다.

그렇다면 이런 경우에는 매번 새롭게 추가된 리스트 아이템까지 클릭 이벤트 리스너를 일일이 달아줘야 할까?

  • 리스트 아이템이 많아지면 많아질수록 이벤트 리스너를 다는 작업 자체가 번거로워지는데, 이 번거로운 작업을 해결할 수 있는 방법이 바로 이벤트 위임(Event Delegation)이다.
// var inputs = document.querySelectorAll('input');
// inputs.forEach(function(input) {
// 	input.addEventListener('click', function() {
// 		alert('clicked');
// 	});
// });

var itemList = document.querySelector('.itemList');
itemList.addEventListener('click', function(event) {
	alert('clicked');
});

// 새 리스트 아이템을 추가하는 코드
// ...
  • 화면의 모든 인풋 박스에 일일이 이벤트 리스너를 추가하는 대신 인풋 박스의 상위 요소인 ul 태그, .itemList에 이벤트 리스너를 달아놓고 하위에서 발생한 클릭 이벤트를 감지한다. 이 부분이 앞에서 봤던 이벤트 버블링이다. 코드를 바꾸고 실행하면 다시 정상적으로 동작하는 것을 확인할 수 있다.
post-custom-banner

0개의 댓글