Javascript

event stopPropagation 이란?

이커머스 웹사이트 클론 코딩을 하며 React를 배우던 중, 모달이 원하는 대로 동작하지 않았다. 똑같은 실수를 하지 않기 위해 이 글을 적기로 했다.

What I tried

모달을 열고 닫는 onClick 이벤트를 만들고 싶었다. 그러나 아래에 코드를 적었을 때 모달 바깥 부분을 클릭했을 때 닫히지 않고 계속 열려 있었다.

function Modal({ setShow }) {
  return (
    <div
      className="absolute bg-[rgba(0,0,0,0.4)] inset-0 flex items-center justify-center"
      onClick={() => setShow(false)}
    >
      <div className="bg-white w-[300px]">This is a modal</div>
    </div>
  )
}
 
function App() {
  const [show, setShow] = useState(false)
 
  return (
    <div>
      <div onClick={() => setShow(true)}>
        <p>Click to open modal</p>
        {show && <Modal setShow={setShow} />}
      </div>
    </div>
  )
}

MDN 글을 읽고, 그제서야 이러한 문제들이 javascript가 이벤트를 처리하는 방법인 Event Bubbling과 Event Capturing 때문에 일어난 것임을 알았다.

Event Bubbling

<div>
  <h1>This is h1</h1>
  <h2>This is h2</h2>
</div>

위에 있는 모든 태그가 자기 자신의 태그 이름을 print하는 onClick 이벤트를 가지고 있다고 가정하자. 만약 h1 태그를 클릭하게 되면 Event Bubbling 때문에 h1 태그와 div 태그가 모두 출력될 것이다. Event Bubbling은 이벤트를 부모한테 넘겨준다.

Event Capturing

Event Capturing은 Event Bubbling과 반대로 이벤트를 처리한다: 이벤트를 자식한테 넘겨준다. 이는 javascript의 기본 옵션이 아니기에 Event Capturing을 사용하기 위해서는 아래에 코드대로 작성해야 한다.

element.addEventListener('click', handleClick, { capture: true })

stopPropagation

stopPropagation은 Event Bubbling과 Event Capturing을 막기 위해 사용된다. 아래에 하이라이트된 코드를 추가하자 의도한대로 코드가 돌아가기 시작했다.

function Modal({ setShow }) {
  return (
    <div
      className="absolute bg-[rgba(0,0,0,0.4)] inset-0 flex items-center justify-center"
      onClick={(e) => {
        e.stopPropagation()
        setShow(false)
      }}
    >
      <div className="bg-white w-[300px]">This is a modal</div>
    </div>
  )
}
 
function App() {
  const [show, setShow] = useState(false)
 
  return (
    <div>
      <div onClick={() => setShow(true)}>
        <p>Click to open modal</p>
        {show && <Modal setShow={setShow} />}
      </div>
    </div>
  )
}

Then, what is this for?

그러면 Event Bubbling과 Event Capturing을 활용하는 방식은 없는 것인가라는 의문이 들었다. 이 둘은 드롭다운 메뉴를 구현할 때 매우 유용하다.

function Dropdown() {
  return (
    <div
      onClick={(e) => {
        e.stopPropagation()
        console.log(e.target.innerText)
      }}
    >
      <div>Hello</div>
      <div>There</div>
    </div>
  )
}
 
function App() {
  const [show, setShow] = useState(false)
 
  return (
    <div>
      <div onClick={() => setShow((prev) => !prev)}>
        <p>Click to open Dropdown</p>
        {show && <Dropdown />}
      </div>
    </div>
  )
}

위의 코드대로 작성하게 되면 각각의 메뉴에 onClick 이벤트를 추가하지 않고도 무슨 메뉴가 눌렸는지 확인할 수 있다.

Thoughts

가끔은 공부할 때 코드가 무엇을 하는지 모르는 상태로 베낄 때도 많았다. 그러나 이러한 경험으로 라이브러리나 프레임워크보단 언어 그 자체에 대해서 더 배워야 함을 깨달았다.