블로그 이미지
프론트엔드 개발도 하고 뛰기도 하고 이챙(leechaeng)
🐻 전체 : 오늘 : 어제 :

공통 컴포넌트를 건드리지 않고 이벤트 제어하기 - 캡처링

2025. 9. 19. 13:43issue

캡처링

최상위 요소(winow)에서 타겟 요소 까지 내려가는 단계, 하향식 전파

  • 최상위 요소부터 시작
  • 실제 클릭한 타겟까지만 내려가면서 이벤트 실행
  • 타겟에 도달하면 캡처링 단계 끝
<html>
  <body>
    <div id="wrap">
      <div id="parent">
        <div id="child"></div>
      </div>
    </div>
  </body>
</html>
wrap.addEventListener('click', () => console.log('wrap'), true);
parent.addEventListener('click', () => console.log('parent'), true);
child.addEventListener('click', () => console.log('child'), true);

1. wrap을 클릭하면?
wrap부터 시작 했으므로 끝
-> console에 wrap이 찍힘

2. parent를 클릭하면?
wrap부터 시작했으므로 끝
-> console에 wrap, parent 찍힘

3. child를 클릭하면?
wrap -> parent -> child 순으로 끝.
-> 콘솔에 wrap, parent, child 찍힘

 

원인

토글 형식의 드롭다운 버튼 컴포넌트를 사용해야 하는 상황이었다. 이 컴포넌트는 버튼을 클릭할 때마다 CSS 클래스가 토글되면서 하단에 하드코딩된 리스트가 펼쳐지거나 접히는 UI였다.

문제는 두 가지였다

  1. Disabled 상태에서의 추가 처리 필요: 컴포넌트가 비활성화된 상태일 때는 토글 대신 알럿을 띄워야 했다
  2. 의도치 않은 렌더링: 다른 탭으로 이동 후 돌아올 때 컴포넌트가 재렌더링되면서 리스트가 저절로 펼쳐지는 현상이 발생했다.

공통 컴포넌트이다 보니 내부에는 이미 고정된 클릭 이벤트가 적용되어 있었고, 직접 수정할 수 없는 상황이었다. 😓😓 
리스트 상단에 탭이 있었는데 탭이 변경될 때마다 리스트가 자동으로 펼쳐지는 이슈가 있었는데, 이는 공통컴포넌트 내부에서 open함수가 호출되도록 구현되어 있었기 때문이었다. 상위에서 이벤트 제어를 할 필요가 있어야 했는데 내부 로직에 호출된 함수라 건들 수 없었다.

  • 공통 컴포넌트 특성상 내부에 고정된 클릭 이벤트가 적용되어 있었고, 직접 수정은 불가능했다.
  • 리스트 상단의 탭이 변경될 때마다 리스트가 자동으로 펼쳐지는 이슈가 있었는데, 이는 공통 컴포넌트 내부에서 open 함수가 호출되도록 구현되어 있었기 때문이었다. 내부 로직에 정의된 함수라 수정할 수 없는 상황이었다.

 

🔥해결 시도

const wrapper = useRef();
wrapper.current.querySelector('.dropdown').classList.remove('open');

처음에는 컴포넌트를 감싸는 wrapper에 ref를 설정하고, querySelector로 내부 엘리먼트를 찾아서 리스트가 펼쳐지거나 disabled시 클래스를 강제로 제거하는 방식으로 접근했다.

하지만 이 방법은 여러 문제가 있었다.

  • 타이밍 이슈로 인한 불안정한 동작(됬다가 안됬다가 ;;;)
  • 코드 복잡성 증가 (다른 로직에도 remove를 처리 해야하는)
  • 근본적인 해결이 아닌 임시방편적 처리

이런 식으로 계속 처리하다 보니 코드가 복잡해지기만 했고, 분명 더 좋은 방법이 있을 거라는 생각이 들었다.

 

해결

공통 컴포넌트 내부엔 이미 click 이벤트가 정의 되어 있으니 클릭했을때 이벤트를 막아야겠다. 라는 생각이 들었다.

 <div @click.capture="onClickEvent">
   <공통 컴포넌트/>
 </div>


onClickEvent(e) {
  e.stopPropagation()
}


버튼 컴포넌트 상위에 div를 감싸고 클릭 이벤트를 걸어주었다.
공통 컴포넌트에서 이미 버블링 단계로 이벤트가 일어나고 있기 때문에 상위에서는 capture 옵션을 사용해 캡처링 단계에서 이벤트를 가로채는 방법을 적용했다.

@click.capture : 캡처링 단계. 이벤트를 감지
e.topPropagation : 이후 이벤트 전파 중단

이렇게 하면 공통 컴포넌트 내부 에서 발생되는 고정된 클릭 이벤트(버블링)를 막을 수 있고 상위 컴포넌트에서 이벤트를 제어 할 수 있다.

중복으로 흩어졌던 classList.remove 코드를 제거해 로직이 단순해졌으며, 기존에 두가지 이슈가 해결됬다.

 

그림으로 표현하면 이렇다.

Before

버튼 클릭 -> 버튼 클릭시 버블링이 일어나면서 상위 요소까지 전파 -> 이벤트 제어 x

After

버튼 클릭 -> 상위 요소가 캡처링 단계에서 이벤트를 잡음 -> e.stopPropagation() 호출로 버튼의 이벤트가 버블링해서 올라오지 않음 -> 상위 div에서 이벤트 제어

반응형