주의!✨
이 글은 개발을 배워가는 초보 개발자 작성하는 일종의 공부기록입니다.
현업에서 일하시는 분들이나 정석과는 거리가 있을 수 있습니다!
🧼 Event Bubbling
JavaScript
를 공부하면서 이벤트 등을 자주 이용하다 보니 이벤트 버블링으로 인해서 예상치 못한 결과가 나오는 경우도 있었고 이를 이용해서 유용하게 사용하기도 했다. 이벤트 버블링에 대해서 한번 정리해보자.
📸 Event capture
캡처링은 단순하게 말해서 이벤트가 발생했을 때 바깥 요소부터 해당하는 요소까지 찾아들어가는 과정을 말한다. 즉 만약 <Body>
태그 안에 있는 <div>
안에 있는 <span>
에서 이벤트가 발생했다면 이를 찾기 위해서 <html>
=> <body>
=> <div>
=> <span>
의 순서로 찾아가게 된다.
🧋 Event Bubbling
이벤트 버블링은 거품이 생기면서 물 위로 올라가는 이미지를 떠올리면 이해하는데 도움이 된다. 만약 HTML 안의 어떤 요소에 이벤트가 발생되게 되면 캡처링을 통해 이벤트가 발생한 곳을 찾고 역으로 이벤트가 상위 요소로 전파 된다. 즉, 제일 하단의 요소에 이벤트가 발생하게 되면 이 이벤트가 모든 부모, 조상요소에도 전파가 된다는 것이다. 이해하기 편하게 예시를 들어보자.
<!-- html 문서 예시 -->
<body>
<div class = "div1">
<ul class = "ul1">
<li class = "li1"></li>
<li class = "li1"></li>
</ul>
</div>
</body>
// 이벤트 버블링 관찰하기
div1.addEventListener("click", () => {
console.log("Div");
})
ul1.addEventListener("click", () => {
console.log("Ul");
})
li1.addEventListener("click", () => {
console.log("Li");
})
위의 코드를 보면 각 요소들에 이벤트 리스너를 등록해주었다. 만약 여기서 <li>
를 클릭하게 되면 어떤 일이 발생하는지 보자.
분명 li1에 있는 이벤트 리스너만 동작을 해야하지만 부모 요소의 이벤트 리스너 또한 동작했다. 처음에 설명했던 것과 같이 이는 이벤트 버블링으로 인해서 이벤트가 부모 요소까지 전파됐기 때문이다. 이런 이유 때문에 부모 요소에도 이벤트 리스너를 등록해야하는 경우에는 주의가 필요하다.
👍 버블링 해결방법
stopPropagation()
이벤트들이 버블링 되다보면 예상하지 못한 일들이 발생할 수 있기 때문에 제대로 제어하는 것이 중요하다. 이벤트 객체에는 stopPropagation()이라는 이벤트를 호출할 수 있는데 이것을 호출하면, 호출된 이벤트까지만 호출이 되고 그 위로는 이벤트가 버블업 되지 않게 된다.
// 이벤트 버블링 관찰하기
div1.addEventListener("click", () => {
console.log("Div");
})
ul1.addEventListener("click", (e) => {
console.log("Ul");
e.stopPropagation(e);
})
li1.addEventListener("click", () => {
console.log("Li");
})
위와 똑같은 상황에서 ul1
에 e.stopPropagation(e)
를 호출해주었다.
그 결과 ul
의 값까지만 출력되고 상위 요소의 경우 전파가 막아진 것을 확인할 수 있었다.
처음에는 매우 좋은 기능이라고 생각했다. "이벤트의 전파를 막아주다니 만능이자나!!" 하지만, 만약... 동일한 이벤트가 발생했을 때 다른 동작을 원하는 로직이 필요하다면? 매우 곤란한 상황을 겪을 수 있다. 게다가 개발은 나 혼자만 보고 만들고 끝나는 것이 아니기 때문에 내가 적어놓은 코드가 누군가의 로직을 방해할 수도 있다.
👀 Event.preventDefault()
치명적인 문제를 불러올 수 있는 방법을 제외하고 다른 방법에 대해서 봐보자. event.preeventDefault()
는 이벤트의 전파가 아닌 이벤트의 처리를 취소할 수 있는 메서드이다. 다만, 취소할 수 있는 이벤트의 경우에만 가능하다.
간단하게 예를 들면 체크 박스에 클릭 이벤트를 취소시켜서 체크가 되지 않도록 만든다거나 인풋창에 있는 키입력 이벤트를 취소시키고 특정 원하는 인풋만 입력되도록 바꿀 수 있습니다.
이는 이벤트를 처리하는 방법 중의 하나이지 버블링을 막는 방법으로 보기는 어렵다. 그렇다면 이벤트 버블링을 효율적으로 처리할 수 있는 방법은 무엇일까?
✅ e.target ! == e.currentTarget
if(e.target !== e.currentTarget){
return;
}
바로 이벤트가 발생한 요소를 나타내는 event.target
과 현재 이벤트가 전파되고 있는 위치인 e.currentTarget
가 동일하지 않은 경우에 이벤트 리스너를 종료하도록 하는 것이다. 이렇게 작성하는 경우에는 얼마든지 원하는 로직을 추가로 구성할 수 있기 때문에 효율적이다.
🧩 버블링 이용!!
// 댓글 아이콘 버튼 기능 구현
feeds.addEventListener("click", (event) => {
if(event.target.tagName !== "I"){
return;
}
onHeartClick(event);
onDelete(event);
})
이벤트 버블링이 꼭 나쁜 문제인 것은 아니다. 자식 요소에서 이벤트가 발생했을 때 부모요소까지 버블링 되는 것을 이용해서 이벤트를 처리하는 방법도 있다. 위의 코드는 그 예시이다.
feeds
는 인스타그램의 피드들을 포함하고 있는 요소이다. 여기에 클릭 이벤트에 대한 리스너를 등록하였다. 그리고 event.target
의 태그 네임이 I
이 아닌 경우에 함수를 종료하도록 했다. 만약 아이콘이 제대로 선택 되어서 함수가 종료되지 않았다면 이벤트를 받아서 onHeartClick
과 onDelete
라는 함수를 실행하도록 하였다. 동작은 아래와 같다.
이벤트 버블링은 잘 사용하면 복잡한 로직을 더 편하게 구성할 수 있도록 도와줄 수 있다. 또한, 전역변수의 선언을 줄일 수 있기 때문에 전역 공간에 대해서도 이득을 취할 수 있다.
✨이벤트를 처리하는데 있어서 주의할 점에 대해서 항상 기억하면서 효율적인 로직에 대해 고민을 더 해보자. 💪
📕 참고: MDN