이벤트 전달 방식

2022. 9. 26. 16:17프로그래밍 언어/JavaScript

이벤트 등록

<button>TEST<button>
var button = document.querySelector('button');
button.addEventListener('click', foo);

function foo(event) {
	console.log('event');
}

위와 같이 addEventListener() 메소드를 통해 화면에 동적인 기능을 추가할 수 있다.

 

 

 

이벤트 버블링 (Event Bubbling)

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

* 상위 화면 요소 : HTML 트리 구조상 한 단계 위에 있는 요소.

<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', printClassName);
});

function printClassName(event) {
	console.log(event.currentTarget.className);
}

최하위 div 태그인 <div class="three"></div>를 클릭하면 아래와 같은 결과가 실행된다.

three
two
one

div 태그 한 개만 클릭했을 뿐인데 3개의 이벤트가 발생된 것을 볼 수 있다.

 

브라우저는 특정 화면 요소에서 이벤트가 발생했을 때,

그 이벤트를 최상위에 있는 화면 요소까지 전파시킨다.

따라서 클래스명 three → two → one 순서로 div 태그에 등록된 이벤트들이 실행된다.

 

마찬가지로, two 클래스를 갖는 두번째 태그를 클릭했다면,

two → one 순으로 클릭 이벤트가 동작한다.

 

 

 

이벤트 캡쳐 (Event Capture)

이벤트 캡쳐는 이벤트 버블링과 반대로, 상위 요소에서 하위로 진행되는 이벤트 전파 방식이다.

<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', printClassName, {
    	capture: true // default: false
    });
});

function printClassName(event) {
	console.log(event.currentTarget.className);
}

addEventListener() 메소드의 세번째 옵션 객체에 capture: true를 설정하면 이벤트 버블링과 반대방향으로 탐색한다. (4 Line)

최하위 div 태그인 <div class="three"></div>를 클릭하면 아래와 같은 결과가 실행된다.

one
two
three

 

 

event.stopPropagation()

stopPropagation() 메소드는 해당 이벤트가 전파되는 것을 막는다.

이벤트 버블링이 경우, 클릭한 요소의 이벤트만 발생시키고 상위로 전달하지 않는다.

이벤트 캡쳐의 경우, 클릭한 요소의 최상위 요소의 이벤트만 동작시키고 하위 요소들로 전달하지 않는다.

 

앞의 이벤트 버블링과 이벤트 캡쳐 예제에서 사용한 코드의 경우 각각 three, one만 출력된다.

/* Event Bubbling */

var divs = document.querySelectorAll('div');
divs.forEach(function(div) {
	div.addEventListener('click', printClassName);
});

function printClassName(event) {
	console.log(event.currentTarget.className); // three
}
/* Event Capture */

var divs = document.querySelectorAll('div');
divs.forEach(function(div) {
	div.addEventListener('click', printClassName, {
    	capture: true // default: false
    });
});

function printClassName(event) {
	console.log(event.currentTarget.className); // one
}

 

 

 

이벤트 위임 (Event Delegation)

하위 요소에 각각 이벤트를 붙이지 않고 상위 요소에서 하위 요소의 이벤트들을 제어하는 방식.

 

<h1>TO DO LIST</h1>

<ul class="itemList">
	<li>
		<input type="checkbox" id="item1">
		<label for="item1">TASK 1</label>
	</li>
	<li>
		<input type="checkbox" id="item2">
		<label for="item2">TASK 2</label>
	</li>
</ul>
var inputs = document.querySelectorAll('input');
inputs.forEach(function(input) {
	input.addEventListener('click', function(event) {
    	console.log('clicked');
    });
});

투두리스트를 list로 나타낸 코드이다.

 

JavaScript querySelectorAll()를 이용해 화면에 존재하는 모든 input 요소를 가져온 후

각 input 요소에 click Event Listener를 추가했다.

 

만약 여기에서 List Item을 추가해야할 경우 아래와 같이 코드가 추가된다.

 

// ...

// List Item 추가

var itemList = document.querySelector('.itemList');

var li = document.createElement('li');
var input = document.createElement('input');
var label = document.createElement('label');
var labelText = document.createTextNode('TASK 3');

input.setAttribute('type', 'checkbox');
input.setAttribute('id', 'item3');
label.setATtribute('for', 'item3');
labe.appendChild(labelText);
li.appendChild(input);
li.appendChild(label);
itemList.appendChild(li);

 

하지만 이렇게 추가하면 새로 추가된 'TASK 3'을 클릭했을 경우 console.log가 출력되지 않는다.

 

input box에 click Event Listener를 등록한 시점에서 List Item은 2개이고

새롭게 추가된 3번째 List Item은 click Event Listener가 등록되지 않았다. 따라서 새롭게 추가해줘야한다.

 

List Item이 많아질 수록 매번 click Event Listener 를 추가해줘야한다.

이 작업을 이벤트 위임(Event Delegation)을 통해 해결할 수 있다.

 

// var inputs = document.querySelectorAll('input');
// inputs.forEach(function(input) {
//	input.addEventListener('click', function(event) {
//     	console.log('clicked');
//     });
// });

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

// List Item 추가
// ...

input box의 상위 요소인 ul 태그 (.itemList)에 Event Listener를 달안호고 하위에서 발생한 클릭 이벤트를 감지한다. (이벤트 버블링)

이렇게 하면 List Item을 새로 추가할 때 마다 이벤트를 달지 않아도 된다.

 

 

 

이벤트 위임의 장점

  1. 동적인 엘리먼트에 대한 이벤트 처리가 수월하다.
    상위 엘리먼트에서만 이벤트 리스너를 관리하기 때문에 하위 엘리먼트는 자유롭게 추가/삭제 할 수 있다.
  2. 이벤트 핸들러 관리가 쉽다.
    동일한 이벤트에 대해 한 곳에서 관리하기 때문에 각각의 엘리먼트를 여러 곳에 등록하여 관리하는 것 보다 관리가 수월하다.
  3. 메모리 사용량이 줄어든다.
    동적으로 추가되는 이벤트가 없어지기 때문이다.
  4. 메모리 누수 가능성도 줄어든다.
    등록 핸들러 자체가 줄어들기 때문이다.

 

 

 

React에서의 이벤트 위임

🔗  https://github.com/facebook/react/issues/13635

 

Event delegation in React · Issue #13635 · facebook/react

Given that React does attach event handlers to the nodes themselves, does the React team recommend app developers to do event delegation when working with large lists for saving memory? Something l...

github.com

I think you might have misunderstood the answer.
React doesn’t attach your click event handlers to the nodes.
It uses event delegation and listens at the document level.

React의 경우 모든 이벤트가 위임으로 처리되고 있다. 

컴포넌트에서 우리가 on~ (ex. onClick)에 붙여준 모든 이벤트 핸들러들이 실제 dom 노드에 붙여지는게 아니라 document 레벨에서 처리되고 있는것이다.

 

 

 


Reference

 

 

'프로그래밍 언어 > JavaScript' 카테고리의 다른 글

slice, substr, substring 비교  (0) 2023.08.28
디바운싱과 쓰로틀링  (0) 2023.08.03
클로저(Closure)  (0) 2022.09.22
호이스팅(Hoisting)  (0) 2022.09.22
스코프(Scope)  (0) 2022.09.22