프론트단에서 코딩을 하다보면 빼놓을 수 없는 부분이 이벤트에 대한 처리인데 그만큼 중요하고, 알아야 하는 내용도 많은데도 여태까지 무지성으로 하지 않았나? 라는 생각이 들었다.
사실 이벤트라는 단어는 일상생활에서도 자주 들을 수 있는 단어다.
경험치 두배 이벤트라던가.. 아니면 하이퍼 익스프레스 이벤트라던가..
특별 행사라는 느낌으로 많이 쓰지만 프로그래밍에서의 이벤트는 살짝 다르다.
사용자의 행동에 대한 반응, 요소에 대한 상태 변화
정도로 정의할 수 있을 것 같다. 예를 들어
사용자의 행동에 대한 반응에 관련된 이벤트로는
사용자가 마우스를 조작할 땐 마우스 이벤트,
사용자가 키보드를 조작할 땐 키보드 이벤트,
요소에 대한 상태 변화에 관련된 이벤트로는
폼 이벤트(submit,change)
네트워크 이벤트(load,error)
브라우저 이벤트(load,resize)
드래그앤드롭 이벤트(drag,drop)
등등이 있다.
이벤트가 발생했을 때, 이벤트에 대한 처리를 하고 싶다면 이벤트 핸들러 함수를 정의하고 이벤트와 이벤트 핸들러를 연결하는 작업을 해야하는데 이를 이벤트 등록 이라 한다.
<button id="myButton">Click me</button>
라는 버튼 요소가 클릭되었을 때 이벤트를 처리하고 싶다면
const button = document.querySelector('#myButton');
function handleClick() {
console.log('Button clicked!');
}
button.addEventListener('click', handleClick);
쿼리셀렉터로 해당 버튼을 참조한 후 handleClick이라는 이벤트 핸들러에 이벤트에 대한 처리 내용을 작성한다. 마지막으로 addEventListener에 '나는 클릭 이벤트가 발생하면 handleClick이라는 함수를 호출할거야' 라고 적으면 등록 완료!
여담으로 이벤트리스너는 이름을 참 잘 지은 것 같다. 사실 이벤트 등록을 하든 말든 이벤트는 항상 발생한다. 듣는다는 단어도 hear이 아니라 listen인 것도 해당 이벤트를 귀 기울여 듣는 친구를 붙여넣는다고 상상하니 그럴 듯하면서도 재밌다.
import React from 'react';
function handleClick() {
console.log('Button clicked!');
}
function Button() {
return (
<button onClick={handleClick}>Click me</button>
);
}
export default Button;
리액트에서는 jsx문법으로 처리되는데, 그냥 버튼에 onClick 프로퍼티를 주면 끝.
바닐라 자바스크립트에서 요소를 쿼리셀렉터로 참조하고 이벤트리스너를 붙이는 과정을 onClick으로 퉁쳤다고 보면 될 듯!
이제 듣고 싶은 이벤트를 어떻게 등록해서 쓰는진 알았다.
그럼 이제 브라우저는 어떻게 이벤트를 감지하는지 알아보자.

import React from "react";
function handleEvent(event) {
console.log(event.currentTarget.className);
}
function Parent() {
return (
<div className="one" onClick={handleEvent}>
one<Child />
</div>
);
}
function Child() {
return (
<div className="two" onClick={handleEvent}>
two<Grandchild />
</div>
);
}
function Grandchild() {
return <div className="three" onClick={handleEvent}>three</div>;
}
export default Parent;
위와 같이 요소를 클릭했을 때 해당 요소의 클래스 이름을 뱉는 이벤트 핸들러가 등록되어있다고 치자.
당연히 two을 누르면 two만 뜨고, three를 누르면 three만 뜰 꺼라고 생각이 들지만 실제는 two를 누르면 one과 two를 뱉고, three를 누르면 three two one을 다 뱉는다. 이는 이벤트 버블링이라는 현상 때문이다. 기본적으로 브라우저 상에서 이벤트를 감지할 때 하위 요소부터 상위 요소까지 이벤트가 전달된다.

import React from 'react';
function handleEvent(event) {
console.log(event.currentTarget.className);
}
function Parent() {
return (
<div className="one" onClickCapture={handleEvent}>
<Child />
</div>
);
}
function Child() {
return (
<div className="two" onClickCapture={handleEvent}>
<Grandchild />
</div>
);
}
function Grandchild() {
return (
<div className="three" onClickCapture={handleEvent}>
</div>
);
}
export default Parent;
이벤트 캡쳐링은 이벤트 버블링과 반대로 상위 요소부터 탐색해가며 이벤트가 발생한 요소를 감지한다.
버블링과 마찬가지로 하위 요소에서 발생한 이벤트가 상위요소까지 전이되지만 순서가 다르다.
하위 요소에서 이벤트 발생 시, 이벤트 버블링의 경우 '하위 - 상위 - 최상위' 순으로 전이된다면 이벤트 캡쳐링의 경우 '최상위 - 상위 - 하위' 순으로 전이된다.
import React from 'react';
function handleClick(event) {
const target = event.target;
if (target.tagName.toLowerCase() === 'button') {
console.log(target.textContent);
}
}
function Parent() {
return (
<div onClick={handleClick}>
<Child />
</div>
);
}
function Child() {
return (
<div>
<button>Button 1</button>
<button>Button 2</button>
<button>Button 3</button>
</div>
);
}
export default Parent;
이벤트 위임은 요소마다 이벤트 등록하는 거추장스러움을 개선해 줄 수 있다.
위와 같이 부모에 이벤트를 등록하고 이벤트 핸들러에서 한번에 처리를 하면
자식 요소마다 일일히 이벤트를 등록해줄 필요가 없다!!
이 메서드는 propaganda(선동) 와 어원이 비슷해보이는 단어가 들어감에도 알 수 있듯이
이벤트 캡쳐링과 버블링을 막는 역할을 한다. 따라서 위 예시들의 이벤트 핸들러에 stopPropagation()가 추가된다면 three를 클릭한다면 이벤트 버블링은 three만 (상위로 전달되지 않음) 이벤트 캡쳐링은 one만 (하위를 탐색하지 않음) 출력될 것이다.
이 메서드는 요소 및 이벤트가 가지고 있는 기본적인 동작으로 발생할 수 있는 side effect를 방지한다.
예를 들어 form 태그의 경우, submit 이벤트는 기본적으로 새로고침이라는 side effect를 동반하는데, submit 이벤트에 ajax나 fetch 등 비동기식으로 데이터를 통신하고 싶다면 preventDefault를 통해 페이지가 다시 로드되는 걸 방지해야 한다.
ChatGPT 센세
https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/