Front-End

이벤트(Event) 흐름 제어하는 법

realhee 2022. 2. 11. 01:00
반응형

 

 

Event란?

웹을 탐색하는 동안 브라우저는 여러 종류의 이벤트를 등록한다.

브라우저에서의 event란 사용자가 클릭했을 '때', 스크롤을 했을 '때', 텍스트를 입력했을 '때'와 같은 것을 의미한다.

 

 

Event를 알아야하는 이유

  • input 창에 값을 입력하고, 엔터를 쳤을 때 리스트를 추가하려고 할 때
  • 삭제 버튼으로 리스트를 삭제하고 싶을 때
  • 인터랙티브 한 사용자 경험(UX)을 만들고 싶을 때

동적인 웹은 사용자와 상호작용이 잘 되어야 한다. 그리고 그 상호작용은 어떤 이벤트에 의해 발생하며 이를 유연하게 다룰 줄 알아야 interactive 한 웹 앱 구현이 가능하다. 프론트엔드에서 많은 인터렉션은 모두 Event(사건)를 처리하는 것과 깊은 관련이 있다. 

 

 

Event의 종류

사용자 인터페이스(UI)이벤트

브라우저가 로드한 HTML 페이지가 아닌 브라우저 창을 사용할 때 발생하는 이벤트다.

예를 들어, 페이지가 로드되었다거나 브라우저 창의 크기가 조정된 경우에 발생하는 이벤트를 말한다.

 

onblur(객체가 focus를 잃었을 때)

onchange(객체의 내용이 바뀌고 focus를 잃었을 때)

onclick(객체를 클릭했을 때)

ondblclick(더블클릭할 때)

onerror(브라우저가 자바스크립트 오류를 만났거나 요청한 자원이 존재하지 않을 때)

onfocus(객체에 focus가 되었을 때)

onkeydown(키를 눌렀을 때)

onkeypress(키를 누르고 있을 때)

onkeyup(키를 눌렀다 뗐을 때)

onload(문서나 객체가 로딩되었을 때), onunload(웹 페이지가 unload 될 때 (새로운 페이지를 요청한 경우))

onmouseover(마우스가 객체 위에 올라왔을 때)

onmouseout(마우스가 객체 바깥으로 나갔을 때)

onreset(Reset 버튼을 눌렀을 때)

onresize(객체의 크기가 바뀌었을 때)

onscroll(스크롤바를 조작할 때)

onsubmit(폼이 전송될 때) 

 

 

 

키보드 이벤트

사용자가 키보드를 이용할 때 발생한다. keydown이나 keypress이벤트에 대한 event 객체는 어떤 키가 눌렸는지를 알려주는 속성을 가지고 있다.

 

keydown : 사용자가 키를 처음 눌렀을 때(키가 눌린 동안은 계속해서 발생)
keyup : 사용자가 키를 뗄 때
keypress : 사용자가 키를 눌렀다가 떼어서 문자가 화면에 나타나게 되면 발생한다.

화살표 키를 누를 때 keydown 이벤트는 발생하지만 이 이벤트는 발생하지 않는다.

사용자가 키를 계속 누르고 있으면 이벤트가 반복적으로 발생한다.

 

 

 

마우스 이벤트

마우스를 움직이거나 버튼을 클릭했을 때 발생한다.

 

click : 마우스를 클릭했을 때
dblclick : 마우스를 더블 클릭했을 때
mousedown : 마우스를 누르고 있을 때
mouseup : 요소 위에서 누르고 있던 마우스를 뗄 때
mousemove : 마우스를 움직일 때(터치스크린에서는 동작하지 않음)
mouseover : 요소 위로 마우스를 움직였을 때(터치스크린에서는 동작하지 않음)
mouseout : 요소 바깥으로 마우스를 움직였을 때(터치스크린에서는 동작하지 않음)

 

 

 

포커스 이벤트

사용자가 상호작용을 할 수 있는 링크나 폼 요소 같은 HTML 요소는 포커스를 받을 수 있다. 이런 요소들은 자신이 포커스를 갖거나 잃을 때 이벤트를 발생시킨다. 

 

focus/focusin : 요소가 포커스를 얻었을 때

blur/focusout : 요소가 포커스를 잃었을 때

 

Event 객체

- event.target : 이벤트가 발생한 요소(사용자가 의도한 가장 명확한 요소)
- event.type : 발생한 이벤트의 종류

- event.key : 키보드를 친 경우 어떤 키를 쳤는지 알 수 있는 요소

 

DOM Element에 이벤트 바인딩하기

이벤트 리스너(Event Listener) 활용

function onAddStationHandler() {
  // 이벤트 처리 로직을 구현한다.
}
// 지하철 역 추가 버튼의 요소를 가져온다.
const $stationAddButton = document.querySelector("#station-add-btn"); 
// 클릭 이벤트가 발생했을 때 onAddStationHandler함수가 실행되도록 바인딩 한다.
$stationAddButton.addEventListener('click', onAddStationHandler);
  1. click: 대기하고자 하는 이벤트. 위 예제에서는 클릭 이벤트를 기다린다.
  2. onAddStationHandler :이벤트가 발생했을 때 실행될 코드. 예제에서는 onAddStationHandler() 함수를 사용한다. 이때 괄호를 생략하는 이유는 괄호를 사용하면 이벤트가 발생할 때가 아니라) 이벤트가 바인딩될 때 함수를 호출하라는 의미가 되기 때문이다.

 

 

매개변수를 가진 이벤트 핸들러와 리스너

매개변수를 전달하고 싶다면 익명 함수(anonymous function) (opens new window)를 이용해야 한다.

$stationAddButton.addEventListener('blur', function() { 
  // 익명함수는 두 번째 매개변수처럼 동작하며, 이름을 가진 함수를 둘러 싼다.
  onAddStationHandler(name);
})

익명 함수는 괄호를 가지고 있기는 하지만 이벤트가 발생했을 때만 실행된다. 이름을 가진 함수는 익명 함수가 호출될 때만 실행되며, 매개변수를 사용할 수 있게 된다.

 

 

 


 

이벤트가 발생하는 순서를 이벤트 흐름(event flow)이라고 하며, 이벤트가 흐르는 방식은 다음의 두 가지가 있다.

 

 

Event 흐름 제어하기

이벤트 버블링 - Event Bubbling

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

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

이벤트 버블링

 

 

여기서 주의해야 할 점은 만약 이벤트가 특정 div 태그에만 달려 있다면 위와 같은 동작 결과는 확인할 수 없다.

“Trigger clicks all the way up”

 

 

이벤트 캡처

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

이벤트 캡처링

특정 이벤트가 발생했을 때 최상위 요소인 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를 설정해주면 된다.

그러면 해당 이벤트를 감지하기 위해 이벤트 버블링과 반대 방향으로 탐색하게 된다.

 

 


 

event.preventDefault()

preventDefault 메소드는 태그의 기본 동작(예를 들면, a 태그는 클릭 시 링크이동, form 태그은 폼 내용 전송)을 하지 않게 막아주는 역할을 한다.

 

링크를 클릭하거나 폼을 제출하는 것과 같은 몇 가지 이벤트들은 사용자에게 다른 페이지를 보여준다. 이런 요소들의 기본 동작을 중단하기 위해서는(예를 들면, 사용자가 링크를 클릭하거나 폼을 제출해도 계속 같은 페이지에 머무르게 하려면)event 객체의 preventDefault() 메서드를 사용하면 된다.

 

event.stopPropagation()

stopPropagation 메소드는 태그를 클릭 시 이벤트의 캡처링이나 버블링을 중단한다.

 

이벤트가 부모 요소로 버블링되는 것을 (특히 부모 요소가 동일한 이벤트에 대해 개별적인이벤트 핸들러를 가지고 있는 경우라면 더더욱) 중단하고 싶을 수 있다.이벤트 버블링을 중단하려면 event 객체의 stopPropagation() 메서드를 이용하자.

 

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
}

// 이벤트 캡쳐 예제
divs.forEach(function(div) {
	div.addEventListener('click', logEvent, {
		capture: true // default 값은 false입니다.
	});
});

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

 

 

이벤트 위임 - Event Delegation

이벤트 위임은 실제 바닐라 JS로 웹 앱을 구현할 때 자주 사용하게 되는 코딩 패턴이다.

 

이벤트 위임을 한 문장으로 요약해보면

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

 

TodoList를 예로 들어보자.

 

2개의 TodoListItem은 개별적으로 click 이벤트 발생 시 check항목을 표시하는 기능으로 구현되어 있다.

그런데 만약 여기서 할 일이 더 생겨서 리스트 아이템을 추가하면 어떻게 될까?

 

새로 추가된 리스트 아이템에는 클릭 이벤트 리스너가 동작하지 않는다.

 

click 이벤트 리스너를 추가하는 시점에서 TodoListItem 개수는 기존의 2개이다.

따라서, 새롭게 추가된 TodoListItem 에는 click 이벤트 리스너가 등록되지 않았다.

이런 식으로 매번 새롭게 추가된 TodoListItem까지 click 이벤트 리스너를 일일이 달아줘야 할까?

 

TodoListItem 이 많아지면 많아질수록 이벤트 리스너를 다는 작업 자체가 매우 번거로워진다.

이 번거로운 작업을 해결할 수 있는 방법이 바로 이벤트 위임(Event Delegation)이다.

 

이벤트 위임의 장점들

이벤트 위임을 이용하면 동적으로 추가되는 요소에도 자동적으로 이벤트가 바인딩되도록 할 수 있다.

 

  • 동적으로 추가되는 요소들에도 동작한다.
    • DOM트리에 새로운 요소를 추가하더라도 이벤트에 대한 처리는 부모 요소에게 위임되었기 때문에 새로운 요소에 이벤트 핸들러를 다시 지정할 필요가 없다.
  • 코드의 간결
    • 이 기법을 이용하면 함수를 많이 작성할 필요가 없으며 DOM과 코드 간의 연결이 간소해져 결과적으로 유지보수에 도움이 된다.

 

<ul id="parent-list">
	<li id="item1">Item 1</li>
	<li id="item2">Item 2</li>
	<li id="item3">Item 3</li>
</ul>
//상위 노드에 이벤트 설정
document.getElementById("parent-list").addEventListener("click", function (e) {            
   if (e.target && e.target.nodeName == "LI") {                
      console.log(`List item  ${e.target.id} was clicked!`);
    }
 });

 

화면의 모든 인풋 박스에 일일이 이벤트 리스너를 추가하는 대신 이제는 인풋 박스의 상위 요소인 ul 태그,. itemList에 이벤트 리스너를 달아놓고 하위에서 발생한 클릭 이벤트를 감지한다.  --> 이벤트 버블링

 

 

과제 : 위 코드는 현재 인풋 박스의 이벤트만 다루는 것이 아니라 label 태그의 이벤트도 감지한다.

event 객체를 이용하여 인풋 박스의 이벤트만 감지할 수 있도록 구현해보자.

 

 

 

 


참고링크

이벤트 버블링, 이벤트 캡처 그리고 이벤트 위임까지

 

반응형