이번에 하고 싶은 문제에서 이벤트 버블링 문제가 발생되었다.프로젝트가 의도한 바는 좋아요 버튼을 클릭했을 때 해당 동작만 동작하는 것이다. 그런데 실제로 동작되는 로직은 자녀태그의 onClick이 실행되고 난 직후 바로, 부모태그에 설정된 onClick도 더불어서 실행되었다는 것이다. 나는 자녀태그의 onClick만 실행되기를 기대했지만, 자녀태그 이후 부모태그의 onClick이 따라서 실행된 것이다.
공식문서에 의하면 "React는 이벤트들을 다른 브라우저에서도 같은 속성을 가지도록 표준화"한다고 한다. 무언가 추가적인 설명이 필요하다.
먼저 브라우저 이벤트에 대해서 살펴보자. 브라우저 이벤트란 사용자의 응답에 반응하여 브라우저에서 발생하는 다양한 이벤트들을 말한다. 가령 사용자가 마우스를 클릭했다거나, 키보드 입력을 한 것에 반응하는 것(onClick, onMouseOver, onKeyDown 등)을 말한다.
리액트에서는 이러한 브라우저가 제공하는 이벤트를 추상화하여 제공하는데 이를 용어상 합성 이벤트(SyntheticEvent)라 부르는 것이다. 추상화 과정에 대한 설명이 어렵지만, 리액트는 이 과정을 통해서 브라우저 및 플래폼에서 동작할 이벤트를 일괄적으로 처리하여, 리액트 애플리케이션의 성능을 향상시킨다. 이는 이벤트 호출을 일괄적으로 처리함으로 불필요하게 발생할 수 있는 리소스의 발생을 억제하기 때문이다. 즉 이 과정에서 일괄적이라는 말이 바로 "합성"의 의미를 ㅈ비니게 되었음으로, 합성이벤트라는 이름이 붙여지게 되었다.
브라우저의 이벤트에 호환되기 때문에 리액트에서도 이벤트 발생에 있어서 캡처링과 버블링에 직면하게 된다. 진행되는 말이 어려울 수 있으니, 코드로 먼저 살펴보자.
function ExampleComponent() {
function handleClick(event) {
console.log(event.currentTarget.tagName, event.type);
}
return (
<div onClickCapture={handleClick} onClick={handleClick>
<div onClickCapture={handleClick} onClick={handleClick}>
<div className="me" onClick={handleClick}>Click me!</div>
</div>
</div>
);
}
className="me"에 해당하는 div 태그를 클릭했다고 하자. 콘솔에는 어떤 결과가 산출될까?
className="me"를 클릭하면 캡처링에 의해서 조부모 태그로부터 시작되어 이벤트가 발생되고, 그들의 onClick을 실행하지 않았음에도 자동으로 onClick이 실행되는 버블링이 발생되었다.
캡처링이란 엘리먼트의 상위 엘리먼트로부터 시작해서 이벤트 타켓인 엘리먼트까지 이벤트가 전파되는 발생으로, className="me"의 이벤트가 선언되면 그 이벤트에 도달하면서 상위 엘리먼트들의 이벤트 헨들러를 실행하는 캡처링이 발생된다. 즉 이 단계에서도 어떠한 기능을 선행하는 동작을 부여할 수 있다는 말이다.
버블링이란 반대로, 이벤트라 발생한 엘리먼트에서부터 상위 엘리먼트까지 이벤트가 전파되는 방식으로, 상위 엘리먼트로 나아가며 그들의 이벤트 핸들러를 실행시킨다.
<Artgrambox onClick={() => openModalhandle(artgramId)}>
<Likes onClick={(event)=> {
// event.stopPropagation()
alert("안녕")}} />
</Artgrambox>
위의 코드는 전체 코드를 요약한 부분으로 내가 의도한 바는 Likes 컴포넌트를 실행하면 경고창이 뜨도록 하고 싶었다. 그런데 의도하지 않게 Artgrambox의 onClick이 이어서 실행되며, openModalhandle 핸들러가 실행되며 모달이 열려버렸다.
나는 후속되는 버블링 없이 딱 Likes의 이벤트만 실행하고 싶었다. 바로 이때 사용하는 이벤트 메서드가 바로 stopPropagation()이다. MDN문서에 따르면, stopPropagation()는 현재 이벤트가 캡처링/버블링 단계에 더 이상 전파되지 않도록 방지한다. 위의 코드에서 각추처리한 부분을 풀면 원하는 동작만 수행이 가능해진다.
오늘도 같은 증상이 발생되었다.
import React from "react";
import * as Artgramparts from "./ArtgramCss";
import { useOpenModal } from "../../../hooks/artgram/useOpenModal";
function ArtgramBox({ info }) {
// Modal 관련 커스텀 훅
const {modalState, openModalhandle} = useOpenModal();
return (
<Artgramparts.ArtgramboxWrap onClick={() => openModalhandle()}>
{/* 상세모달 창 열기 */}
{modalState && (
<ArtgarmDetailModal modalState={modalState} openModalhandle={openModalhandle}/>
)}
</Artgramparts.ArtgramboxWrap>
);
}
export default ArtgramBox;
ArtgramBox 컴포넌트를 클릭하면, 이벤트가 발생되며 modalState의 상태가 변경되는 useOpenModal이 동작한다. 이를 통해서 ArtgarmDetailModal컴포넌트가 동작되도록 실행하였다. 문제는 ArtgarmDetailModal컴포넌트에서 발생했다. 내부로직 안에 아무런 설정을 하지 않아도 모달이 닫혀지는 현상이 발생했기 때문이다. 이 역시도 버블링으로 인해 발생된 문제였다. 즉 상위컴포넌트인 부모컴포넌트의 이벤트가 활성화되어 있기 때문에, ArtgarmDetailModal 역시 코드 상으로는 ArtgramBox 내에 있는 요소인 셈인 것이다. 이를 위해서는 버블링을 끝어줘야 한다. 이를 위해서 아래와 같이 코드를 작성함으로 원하는 동작이 실현되도록 하였다.
import React from "react";
import * as Artgramparts from "./ArtgramCss";
function ArtgarmDetailModal(modalState, openModalhandle) {
return (
<>
<Artgramparts.ModalBackground
state={modalState}
onClick={(e)=>e.stopPropagation()}
/>
<Artgramparts.ModalWindow state={modalState}
onClick={(e)=>e.stopPropagation()}/>
</>
);
}
export default ArtgarmDetailModal;
느낀점은 컴퓨터는 설정한 대로 움직인다는 것이다. 그러기에 세부적인 설정까지 고려하여 코드를 작성하는 것이 얼마나 중요한지에 대해서 다시금 깨닫는다.