컨테이너 안에 버튼이 존재하는 컴포넌트를 구현하는 중에, 버튼을 클릭했을 때, 이벤트 버블링이 발생하여 컨테이너에 등록된 클릭 이벤트 핸들러가 동작하는 문제가 있었습니다.
이벤트 버블링 떄문에 발생하는 문제라는 것은 알고 있었지만, 어디에서나 자주 써왔던preventDefault를 사용해서 막을 수 있을 줄 알고 사용했다가 계속 이벤트버블링이 발생하는 걸 보고 당황했습니다. 몇 분동안 헤매다 stopPropagation의 존재를 깨닫고 이벤트버블링을 막을 수 있었습니다. 이 참에 두 메소드의 차이점을 정리해놓습니다.
stopPropagation()과 preventDefault()는 자바스크립트와 웹 개발에서 이벤트 처리와 관련하여 사용되는 메소드입니다. 이들의 주요 차이점은 아래와 같습니다.
이 두 메소드의 차이점은
제가 겪었던 문제는 다음과 같습니다. 이미 버튼을 클릭했을 때 이벤트가 동작하는 컴포넌트가 존재하는 상황에서, 버튼을 감싸고 있는 컨테이너를 클릭했을 때도 다른 이벤트가 동작하는 기능을 추가해야 했습니다. 아래는 예시 코드입니다.
function App() {
const [contents, setContents] = useState('')
return (
<div className="App">
<section className='py-16 px-10 flex flex-col items-center'>
<div className='shadow-lg rounded-md hover:bg-green-500 border w-60 h-28 flex justify-center items-center'
onClick={() => { setContents("container click!") }}>
<button className='p-5 rounded-full bg-gray-200 hover:bg-gray-300 shadow-xl border-2' onClick={() => { setContents("button Click") }}>
<IcPower />
</button>
</div>
<p className='text-green-400 font-bold text-lg'>{contents}</p>
</section>
</div>
);
}
이 코드에 따르면 div 영역을 클릭했을 때, "container click!" 이라는 문구가 나타나야 하고, 가운데 전원 버튼을 클릭했을 때는 "button Click" 문구가 나타냐야 합니다..
그런데 버튼을 클릭했을 때에도 다음과 같이 "container click!"이라는 문구가 나타났습니다.
사실 이 문제는 이벤트 버블링(event bubbling) 때문에 발생합니다. 웹 브라우저에서는 이벤트가 자식 요소에서 부모 요소로 전파됩니다. 이 경우, 버튼을 클릭하면 먼저 버튼에 대한 클릭 이벤트가 발생하고, 이벤트가 부모의 div까지 버블링됩니다. 따라서 두 이벤트 핸들러가 모두 호출되어 버튼을 클릭했을 때 "button Click"과 "container click!" 두 문구가 순차적으로 나타납니다.
이를 방지하기 위해 버튼의 onClick 이벤트 핸들러에서 event.stopPropagation()을 호출하여 이벤트 버블링을 중단시켜야 합니다. 이렇게 하면 버튼 클릭 이벤트가 div로 전파되지 않습니다.
//stopPropagation을 호출
function App() {
const [contents, setContents] = useState('')
return (
<div className="App">
<section className='py-16 px-10 flex flex-col items-center'>
<div className='shadow-lg rounded-md hover:bg-green-500 border w-60 h-28 flex justify-center items-center'
onClick={() => { setContents("container click!") }}>
<button className='p-5 rounded-full bg-gray-200 hover:bg-gray-300 shadow-xl border-2'
onClick={(e) => { e.stopPropagation(); setContents("button Click") }}>
<IcPower />
</button>
</div>
<p className='text-green-400 font-bold text-lg'>{contents}</p>
</section>
</div>
);
}
export default App;