본문 바로가기

prev/실전 프로젝트

Event bubbling Trouble Shooting

 💡 문제 인식

  • 모달을 띄운 뒤, 모달 내부의 닫기 버튼이나 모달의 백그라운드를 누르면 모달이 닫히게 closeModal 함수를 등록해주었는데, 모달이 닫히지 않음.
  • 아래는 문제의 코드. closeModal 함수가 modal을 닫게하는 기능을 하고, 백그라운드와 닫기 버튼에 함수를 등록하였지만 동작안함.
    • code(문제가 발생한 곳) 
return (
      <UI.StUploadedFileBlock key={file.eventId} onClick={modalOpenHandler}> // modal 내에서 click 이벤트 발생시, 해당위치의 click 이벤트도 발생
					{modalOpen && (
		        <Modal closeModal={closeModal}>
		          <UploadedDetail
		            data={data}
		            isLoading={isLoading}
		            type={type}
		            closeModal={closeModal}
		          />
		        </Modal>
			      )}
        <UI.StNameDateBlock>
          <UI.StContentSpan>😵‍💫 | {file.userName}</UI.StContentSpan>
          <UI.StDateSpan className="date"> {file.enrollDay}</UI.StDateSpan>
        </UI.StNameDateBlock>
        <UI.StContentSpan>📎 | {file.fileName}</UI.StContentSpan>
      </UI.StUploadedFileBlock>
  );
  • Modal Open/Close 관련 코드
    1. closeModal, Modal 나오는 조건
const closeModal = () => {
    setModalOpen(false);
    console.log('test');
  };


{modalOpen && (
        <Modal closeModal={closeModal}>
          <UploadedDetail
            data={data}
            isLoading={isLoading}
            type={type}
            closeModal={closeModal}
          />
        </Modal>
      )}

2. Modal 컴포넌트내에서 백그라운드 클릭 시 closeModal() 호출 

<StModalBackground
        background={background}
        onClick={() => closeModal()}
></StModalBackground>
  • Modal은 UploadDetail을 children 으로 받아서 모달에서 보여준다.
  • UploadDetail에서 props로 closeModal을 받아서 button의 onClick에 넣어주었다. 

 

 🚫 문제 분석

  • Modal의 Open/Close 관련된 내용에 Log를 찍어 전반적인 흐름 파악
    • closeModalconsole.log(’test’) 를 넣어줘서, closeModal이 실행된다면 콘솔에서 확인할 수 있게 세팅했다.
  • 문제가 되는 부분 분석
    • onMouseClick()(백그라운트 클릭)으로 인한 closeModal 시 정상 작동 확인
    • button 으로 인한 closeModal() 시, modalOpen State 값이 변경되지 않아, Modal이 Close 되지 않는 현상 파악
    • 실제적으로 button으로 Click으로 closeModal()을 해서 State의 변화를 일으켰지만, 부모에 있는 onClick()이 실행되서 closeModal()이 재호출되 State 값이 안바뀐것처럼 보이는 문제 발견

 

시도

  • closeModal을 실행시키는 이벤트를 onClick이 아닌 onMouseDown 으로 바꿔봄
    • closeModal이 실행되고, setModalOpen(false)도 실행되며 모달이 정상적으로 닫힘.
  • onClick 시, 왜 부모에 있는 Click 이벤트가 작동하는지에 대한 원인파악
    • 이벤트 버블링 분석 

 🛠 해결 이벤트 버블링 - Event Bubbling

이벤트 버블링은 특정 화면 요소에서 이벤트가 발생했을 때 해당 이벤트가 더 상위의 화면 요소들로 전달되어 가는 특성을 의미한다.

해결 코드 1

  • stopPropagation() 메서드로 이벤트의 전파를 방지한다.
const closeModal = (event) => {
event.stopPropagation(); //stopPropagation()을 사용해 버블링 방지 
setModalOpen(false);
};

해결 코드 2

  • Modal 은 StUploadedFileBlock 안에 들어가 있을 필요가 없으니 따로 빼준다.
  • 더이상 StUploadedFileBlock는 Modal의 상위태그가 아니기 때문에 이벤트 전파가 일어나지 않는다.
return (
      <UI.StUploadedFileBlock key={file.eventId} onClick={modalOpenHandler}>
        <UI.StNameDateBlock>
          <UI.StContentSpan>😵‍💫 | {file.userName}</UI.StContentSpan>
          <UI.StDateSpan className="date"> {file.enrollDay}</UI.StDateSpan>
        </UI.StNameDateBlock>
        <UI.StContentSpan>📎 | {file.fileName}</UI.StContentSpan>
      </UI.StUploadedFileBlock>
		{modalOpen && (
        <Modal closeModal={closeModal}>
          <UploadedDetail
            data={data}
            isLoading={isLoading}
            type={type}
            closeModal={closeModal}
	        />
        </Modal>
      )}
  );

 

 

궁금했던 부분

useEffect로 modalOpen state가 바뀔 시 상태값을 Log로 찍었는데, 상태변화 (false→true→false) 가 찍혀야 되지않나?

: setState는 비동기 함수이기 때문에 state의 변화는 렌더링이 일어난 이후에 바뀌게 된다. 현재 상황은 렌더링 되기전에 일어나는 상황이기 때문에, state상태 변화는 버블링의 마지막 부분인 true 값이 되는것이고 useEffect는 상태값이 변화가 일어나지 않아 Log가 남지 않았다.

 

button 에서 onClick 이벤트 발생 → 콘솔에 ‘test’ 출력 → 상위 div로 onClick 이벤트 전달, 동작 → button의 onClick이벤트인 closeModal이 비동기로 동작 → 상위 div의 openModal도 비동기로 동작 → 비동기로 실행된 결과값인 true만 돌아오게됨.

  • modalOpen 의 상태 업데이트가 일어나기 전에 비동기로 false로 만들고 다시 true로 만들어서 내보냈으니 true인 결과만 보게 된다.

😆 더 알아본 내용

event.target 부모 요소의 핸들러는 이벤트가 정확히 어디서 발생했는지 등에 대한 자세한 정보를 얻을 수 있습니다.

이벤트가 발생한 가장 안쪽의 요소는 타겟(target)요소 라고 불리고, event.target을 사용해 접근할 수 있습니다.

event.target 과 this(= event.currentTarget)는 다음과 같은 차이점이 있습니다.

  • event.target은 실제 이벤트가 시작된 ‘타겟’ 요소입니다. 버블링이 진행되어도 변하지 않습니다.
  • this는 ‘현재’ 요소로, 현재 실행 중인 핸들러가 할당된 요소를 참조합니다.

이걸 통해서 event가 직접 발생한 곳과 event Handler가 있는곳을 통해서도 버블링을 막을 수 있다.

 ⚠️ 필요한 경우를 제외하곤 버블링을 막으면 안되는 이유!! 버블링은 유용합니다. 버블링은 꼭 멈춰야 하는 명백한 상황이 아니라면 막지 않는게 좋다고 한다. 아키텍처를 잘 고려해 진짜 막아야 하는 상황에서만 버블링을 막는데 좋다.

event.stopPropagation()은 추후에 문제가 될 수있는 상황을 만들어 낼수 있습니다.

예를 들어,

  1. 중첩 메뉴를 만들었다 가정하면,각 서브 메뉴(submenu)에 해당하는 요소에서 클릭 이벤트를 처리하도록 하고, 상위 메뉴의 클릭 이벤트 핸들러는 동작하지 않도록 stopPropagation을 적용합니다.
  2. 사람들이 페이지에서 어디에 클릭했는지 등의 행동 패턴을 분석하기 위해, window내에서 발생하는 클릭 이벤트 전부를 감지하기로 결정합니다. 분석 시스템을 도입하기로 합니다. 그런데 이런 분석 시스템의 코드는 클릭 이벤트를 감지하기 위해 document.addEventListener(’click’..)을 사용합니다.
  3. stopPropagation로 버블링을 막아놓은 영역에선 분석 시스템의 코드가 동작하지 않기 때문에, 분석이 제대로 되지 않습니다. 안타깝게도 stopPropagation을 사용한 영억은 ‘죽은 영역(dead zone)’ 이 되어버립니다.

그렇기 때문에 안쓰는걸 권장한다고 한다!!

 

'prev > 실전 프로젝트' 카테고리의 다른 글

onWeekCalendar  (0) 2023.06.13
DropDown Component Trouble Shooting  (1) 2023.06.13