event.stopPropagation() 의 쓸모 (ft. 이벤트 버블링)

event.stopPropagation 그거 언제 쓰는데..

개인 프로젝트를 작업하다가 존재 유무는 알고 있었지만 한번도 써보지 못 했던 event.stopPropagation() 의 필요성을 느끼고 접목하게 되었다.
event.stopPropagation() 을 이해하려면 먼저 이벤트 캡처링이벤트 버블링에 대해 이해할 필요성이 있다.
이에 대해서 어렴풋이 알고는 있었지만, 직접 프로젝트를 진행하면서 이러한 개념을 활용할 일이 생겼고, 그래서 프로젝트를 진행하면서 다시 알게된 것을 정리해보고자 글을 쓰게 되었다.

이벤트 버블링

(예전에 내가 공부하면서 필기한 그림이 있어서 따왔다..)

이처럼 가장 상위 요소 Content 와 그 하위 요소로 list 가 있고, 또 다시 그 하위 요소로 item 들이 있을 때
item 을 클릭하는 이벤트가 발생했을 때 이벤트의 item 의 이벤트만 동작하는 것이 아니라, 그 밑에 list 의 이벤트, 그 다음 또 content 의 이벤트가 동작하게 된다.
=> 이렇게 어떤 요소에 이벤트가 동작했을 때 가장 최상단의 조상 요소를 만날 때까지 이 과정이 반복되면서 이벤트 핸들러가 동작하는 것을 말한다.

그림을 보면 마치 버튼처럼 내가 그려놓았다.
가장 최상단의 item 버튼을 꾹- 누르면 당연히 그 밑에 list 버튼과 content 버튼까지 꾹꾹 눌리게 된다고 생각하면 이벤트 버블링에 대해 이해하는 것이 쉽다.

여기서 내가 어렴풋이만 알면서 다소 헷갈렸던 부분이 있었다.

event.target vs event.currentTarget

위의 예시에서 아래 코드와 같이 작성했다면 어떻게 나올까

item.addEventListenr('click', (e) => console.log(e.target))
item.addEventListenr('click', (e) => console.log(e.currentTarget))

정답은 둘다 item 이 나오게 된다.

그럼 이번에는 어떨까?

list.addEventListenr('click', (e) => console.log(e.target))
list.addEventListenr('click', (e) => console.log(e.currentTarget))

이거는 경우에 따라 다르게 나타난다.

1) 만약에 list 에 이벤트 핸들러를 추가했지만, item 이 위치한 요소를 클릭했다면?

  • 이 경우 event.targetitem 이다.
  • 반면 event.currentTargetlist 다.

(글을 쓰는 나도 조금 헷갈린다)

2) 그럼 item 요소와 겹치지 않는 list 부분을 클릭했다면?

  • 이 경우 event.targetevent.currentTarget 은 은 모두 list 가 나온다

왜 그렇게 될까?
먼저 evnet.target 은 이벤트 핸들러가 걸린 그 직접적인 대상이 아니더라도, 이벤트가 동작하게끔 만들게한 대상(target)을 말한다.
즉 list 에 이벤트를 걸었지만, item 을 클릭함으로써 이벤트 버블링이 발생해 list 의 이벤트가 동작하게 되었다면 이 경우 evnet.targetitem 이 된다.

event.currentTarget 은 오직 해당 이벤트가 걸린 바로 자신을 지칭한다.
즉 list 에 이벤트를 걸었기 때문에 item 요소를 클릭함으로써 list 의 이벤트가 동작했다고 하더라도 event.currentTarget 은 항상 list 가 될 수 밖에 없다.

그럼 항상 event.currentTarget 을 쓰는 것이 좋을까?
그러나 그렇다고 단정지을 수는 없다. 상황에 따라서 적절한 것을 골라쓰는 것이 가장 좋을 것이다.


이벤트 캡처링


(출처 JavaScript.Info https://ko.javascript.info/bubbling-and-capturing)

이벤트 버블링이 있다면 반대로 이벤트 캡처링이 있다.
이벤트 버블링의 반대 개념으로 부모 요소의 이벤트가 하위요소로 전파되는 것을 말한다.

이벤트 캡처링의 예시는 흔치 않다고 하고 나도 실제로 발견한 적이 없어서 적절한 예시를 들기는 힘들 것 같다.
이벤트 캡처링에 대해서는 다음에 더 자세히 알아보도록 하자구..?


그래서 이게 왜 갑자기 중요해졌지?

코드를 작성하던 도중 evnet.targetevnet.currentTarget 의 차이에 의해서 내가 원하는대로 이벤트가 동작하지 않는 상황이 발생했다.

const onOpenCard = (e: React.MouseEvent<HTMLDivElement>) => {
  setCardOpened(true)
  setCardId(e.currentTarget.id)
}

<div key={i} id={room.roomName} onClick={onOpenCard}>
  // ...
</div>

프로젝트를 진행하던 중 숙소 상세페이지를 만들면서 해당 카드를 클릭하면 해당 카드의 id 를 추출한뒤, 그 카드의 id 를 가지고 그와 일치하는 객실 정보를 모달로 띄어야 했다. 그래서 evnet.target.id 로 클릭한 카드의 id 를 추출하려고 했는데 전혀 쌩뚱맞은 빈 문자열 등이 나왔다.
원인을 찾아보니 내가 이벤트를 건 div 요소 위에 다양한 요소들이 더 있었기 때문에 단순히 event.target 으로 걸면 의도한 div 위의 요소들의 id 가 추출되고 있었다.
때문에 이를 event.currentTarget.id 로 수정해서 내가 의도한 div 요소의 id 를 정확히 추출할 수 있었다.

이번엔 다른 문제가 발생했다!

해당 카드 영역 div 요소를 클릭하면 객실 상세정보를 담은 모달창을 띄웠는데, 문제는 '예약하기' 버튼을 클릭해도 모달창이 뜨는 문제점이 있었다. evnet.currentTarget 을 사용하면서 나타난 의도하지 않은 효과였다.

이번엔 또 다른 해결책이 필요했다.

이때 내가 사용한 방법이 바로 event.stopPropagation()

const handleReserve = (e: React.MouseEvent<HTMLButtonElement>) => {
  e.stopPropagation()
  // ...
}

<Button variant="contained" onClick={handleReserve}>예약하기</Button>

내가 클릭하려는 버튼은 오직 버튼의 이벤트만 동작하고 모달창을 띄우는 div 요소의 이벤트는 동작시키지 않고자 했다.
때문에 버튼에 걸은 핸들러 안에서 e.stopPropagation() 을 동작시켜서 div 요소의 이벤트 동작을 막았다.

event.stopPropagation 메서드를 사용하면, item 요소를 클릭했을때 item 버튼은 눌리지만, item 요소의 크기만큼에 한정해서 그 부분에서는 list 와 content 요소는 눌리지 않아서 이벤트가 동작되지 않는다.

그러나 event.stopPropagation() 은 그리 권장되지 않는다.
evnet.stopPropagation() 메서드를 사용한 요소는 말그대로 해당 요소의 이벤트를 제외하곤 모든 버블링을 제한함으로써 이 또한 의도치 않게 이벤트 동작을 저지시킬 수 있다.
때문에 꼭 필요한 부분인지 아닌지를 잘 고려해서 사용을 해야겠다.


이상 프로젝트에 event.stopPropagation() 을 활용하면서 어렴풋이 알고 헷갈렸던 이벤트 버블링과 e.target 과 e.currentTarget 에 대해 알아보았고, 또 이벤트버블링을 막기 위한 event.stopPropagation() 메서드에 대해서 알아본 것을 포스팅해보았다.

아주 자주 활용하는 개념은 아니기 때문에 잘 정리해두고 나중에 헷갈릴 때 다시 보러오면 좋을 것 같다.

참고 목록

좋은 웹페이지 즐겨찾기