2025. 9. 19. 13:43ㆍissue
캡처링
최상위 요소(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였다.

문제는 두 가지였다
- Disabled 상태에서의 추가 처리 필요: 컴포넌트가 비활성화된 상태일 때는 토글 대신 알럿을 띄워야 했다
- 의도치 않은 렌더링: 다른 탭으로 이동 후 돌아올 때 컴포넌트가 재렌더링되면서 리스트가 저절로 펼쳐지는 현상이 발생했다.
공통 컴포넌트이다 보니 내부에는 이미 고정된 클릭 이벤트가 적용되어 있었고, 직접 수정할 수 없는 상황이었다. 😓😓
리스트 상단에 탭이 있었는데 탭이 변경될 때마다 리스트가 자동으로 펼쳐지는 이슈가 있었는데, 이는 공통컴포넌트 내부에서 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에서 이벤트 제어