
프로젝트를 진행하다가 경매글을 모아보는 페이지를 만들게 되었다.
여러 경매글이 있고 경매글을 누르면 상세페이지로 이동하는데 글 마다 이벤트 핸들러를 붙이면 브라우저의 성능이 안좋아지고 가비지 컬렉터에 메모리가 누수가 발생한다고 생각했다.
그래서 이벤트위임을 통해 불 필요한 이벤트핸들러를 최소화해 브라우저 성능을 좋게 만들려고 한다.
그 과정에서 겪은 이슈를 정리해보고자 블로그를 작성하게 되었다.
자바스크립트 코딩을 하다보면 자연스럽게 onClick이벤트나 addEventListener이벤트를 많이 쓴다.
그럼 그 이벤트는 어떤방식으로 브라우저에 전달이 되어서 사용자에게 보여지는걸까?
브라우저에서 어떻게 이벤트들을 감지하고 그 이벤트는 어떤식으로 전달되는지 알아보자.
버블링: 이벤트가 발생했을때 해당 이벤트가
상위요소로 전달되어집니다.
캡처링: 이벤트가 발생했을때 해당 이벤트가하위요소로 전달되어집니다.
버블링과 캡처링을 응용한다면 이벤트위임 이라는걸 사용할 수 있다.
이벤트위임이란 이벤트가 전파(버블링)되는걸 예상해 현재 요소가 아닌 그 위 상위 요소에 이벤트를 설정하는것입니다. 그렇게 되면 이벤트를 보다 적게 설정할 수 있으므로 메모리를 최적화 시킬 수 있다.
이제 하나씩 살펴보자.
버블링은 이벤트가 발생한지점부터 eventTarget노드(최상위 노드)까지 전달되면서 중간에 만나는 이벤트들까지 전부 실행처리해준다.
문제:
앞에 마우스, 마우스패드,책상이 있는데 마우스를 누른다면 마우스패드와 책상은 누른걸까?
정답:브라우저는 하위요소(마우스)를 클릭해도 상위요소(책상)까지 누른걸
로 판정된다.
코드로 예시를 들어보자.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<style>
.table {
width: 300px;
height: 300px;
background-color: red;
}
.mousePad {
width: 150px;
height: 150px;
background-color: orange;
}
.mouse {
width: 75px;
height: 75px;
background-color: yellow;
}
</style>
<body>
<div class="table">
테이블
<div class="mousePad">
마우스 패드
<div class="mouse">마우스</div>
</div>
</div>
</body>
<script>
const table = document.querySelector(".table");
const mousePad = document.querySelector(".mousePad");
const mouse = document.querySelector(".mouse");
table.addEventListener("click", () => {
console.log("table클릭");
});
mousePad.addEventListener("click", () => {
console.log("마우스패드클릭");
});
mouse.addEventListener("click", () => {
console.log("마우스클릭");
});
</script>
</html>
마우스를 누르면 패드와 테이블도 같이 누름판정이 되는걸 확인할 수 있다.
캡처링은 버블링과 반대로 상위요소에서 하위요소로 내려가면서 이벤트를 전달한다.

const table = document.querySelector(".table");
const mousePad = document.querySelector(".mousePad");
const mouse = document.querySelector(".mouse");
table.addEventListener(
"click",
() => {
console.log("table클릭");
},
{ caputure: true }
);
mousePad.addEventListener(
"click",
() => {
console.log("mousePad클릭");
},
{ caputure: true }
);
mouse.addEventListener(
"click",
() => {
console.log("mouse클릭");
},
{ caputure: true }
);
마우스를 눌렀을때 버블링과 반대로 테이블부터 마우스로 점차적으로 내려가면서 이벤트가 발생하는걸 볼수있다!^_^
addEventListener
addEventListener은 첫번쨰 인수에 어떤 이벤트를 발생할껀지 , 두번쨰 인수에는 이벤트가 발생했을때 실행될 코드 마지막으로 3번쨰인수에는 캡쳐링의 유무를 선택할 수 있다.
1,2번쨰 인수는 필수로 적어야하고 3번쨰인수를 적지 않았다면 기본값인 capture:false로 고정된다.
이벤트위임
이벤트 위임입니다. 사실 버블링과 캡처링을 알아야 하는 이유가 이벤트위임을 배우기 위해서라고 해도 맞는말 같다.
캡처링은 잘 활용 안한단.
캡쳐링을 활용한다면 로깅??
만약에 리스트에 엄청 많은 목록이 있다면 그 목록에 전부 클릭이벤트를 달아야할까?
이벤트를 많이 부착할수록 브라우저는 과부하에 걸린다.
이벤트리스너는 항상 web에서 돌기때문에 최대한 적게 사용하는게 좋다.
해결방안으로 목록(하위 태그)에서 발생하는 이벤트를 리스트(상위 태그)에서 관리할 수 있다면 어떨까?
버블링 덕분에 하위 태그에서 발생하는 이벤트를 상위 태그에 이벤트를 부착해도 알 수 있다. 이벤트는 점차적으로 상위태그로 이동하기 떄문이다!╰(°▽°)╯
event.target VS event.CurrentTarget
상위태그에 이벤트를 부착해서 메모리를 최적화할 수 있다는건 알겠는데 사용자가 어떤 하위태그를 클릭했고 어떤 이벤트를 발생시킬지 어떻게 알고 개발자가 코딩을 할까?
이벤트는 엄청 많은 속성을 가지고 있다!
어떤 하위태그를 클릭한지 알수있는 event.target , event.CurrentTarget 대해서 알아보겠다.
event.target은 클릭한 직접적인 요소에 접근할 수 있다.
event.CurrentTarget은 현재 이벤트리스너가 부착한 곳에 접근할 수 있다.

<script>
const table = document.querySelector(".table");
const mousePad = document.querySelector(".mousePad");
const mouse = document.querySelector(".mouse");
table.addEventListener("click", (e) => {
console.log(e.currentTarget); //이벤트 핸들러가 부착된곳
console.log(e.target); //내가 현재 클릭한 요소
});
mousePad.addEventListener("click", () => {});
mouse.addEventListener("click", () => {});
</script>
마우스영역을 클릭한 모습이다.
마우스영역을 클릭할 시 버블링이 발생해서 상위태그 table 이벤트로 버블링이 되었다.
버블링은 브라우저 자체에서 제공하는거라 기본적으로 이벤트가 발생했을때 자동으로 실행되는데
혹여나 버블링을 사용하고 싶지 않다면 event.stopPropation()이라는 함수를 써주면 된다.
코드로 예시를 들어보겠습니다

<script>
const table = document.querySelector(".table");
const mousePad = document.querySelector(".mousePad");
const mouse = document.querySelector(".mouse");
table.addEventListener("click", (e) => {
console.log(e.currentTarget); //이벤트 핸들러가 부착된곳
console.log(e.target); //내가 현재 클릭한 요소
});
mousePad.addEventListener("click", () => {});
mouse.addEventListener("click", (e) => {
e.stopPropagation();
console.log(e.target); //내가 현재 클릭한 요소
});
</script>
mouse 요소를 클릭했을때 e.stopProgation() 함수로 버블링을 중단시켰습니다.
결론적으로 콘솔에는 마우스 요소에 target메시지만 찍히게 되었습니다.
이벤트 중단은 정말 확실할때만 써야하고 처음 썻을땐 괜찮을수도 있지만 나중에 예상치못한 오류로 발전될 수 있기때문에 사용을 웬만하면 안하시는걸 추천한다!


모든 경매글들을 감싸고 있는 부모 div박스에 onclick 이벤트 핸들러를 붙이고 e.target으로 내가 누른 태그요소를 감지한 후 게시글 id를 가져와 라우팅을 해주는걸 생각했다.
하지만 내 생각과 달리 e.target요소가 내가 누른 태그를 가져오는 것이기때문에
프로필을 누르면 프로필 태그가 이미지를 누른다면 이미지 태그가 나왔다.
그럴때 id를 가져올려면 모든 하위 태그에 내가 id를 적어줘야하나?
<div className={styles.userImage}>
<Image data-id={id} src={imageUrl} alt="picture" fill />
</div>
<div className={styles.userInfo}>
<div className={styles.profile}>
<Image data-id={id} src={profile} alt="profile" fill />
</div>
<div data-id={id} className={styles.auctionInfo}>
<p>{stuffName}</p>
<div>
예를 들면 이런식.. 그러면 JSX부분도 더러워지면서 만약 태그가 많다면 일일이 하나씩 다 넣어줘야하나...
이렇게하면 임시적으로 구현은 되지만 다른 방법이 있을꺼같아서 고민하며 딥다이브 책을 찾아봤는데 찾았다!
자바스크립트 문법중에 closest라는게 있었다.
완전 이벤트위임 전용 메소드..!
closest 메소드는 가장 가까운 부모요소를 찾아주는 메소드다.
그렇다면 어떻게 구현하면 될까?
그건 간단했다.
// 게시글 박스
<div id="board" data-id={id} className={styles.board}>
{생략}
</div>
게시글 박스에 id를 지정한다.
그후 이 게시글 박스들을 감싸고 있는 요소를 눌렀을 때 그 누른 e.target 요소에 closest를 사용해 부모 요소 아이디가 board인 태그를 찾는다.
만약 board라는 아이디를 가진 태그가 있다면? 사용자는 board를 누른 것이고 그 부모요소의 아이디를 가져오면 된다 !
const gotoDetailBoard = (e: React.MouseEvent<HTMLDivElement>) => {
const target = e.target as HTMLDivElement;
if (target.closest('#board')) {
const parentChild = target.closest('#board');
const id = parentChild?.getAttribute('data-id');
router.push(`/auction/${id}`);
}
};
이벤트 위임은 여러가지로 쓸 일이 많은거같아서 알아두면 정말 좋은 것같다. 어떤 기능을 구현할 때도 이벤트핸들러가 많이 붙는다면 이벤트위임을 떠올려야겠다.