Swiper 로 만든 슬라이드 안에 버튼을 넣었더니 `stopPropagation` 이 통하지 않는 문제를, 브라우저 이벤트 전파 단계와 React 의 이벤트 위임 위치까지 파고들어 해결한 기록이다. 카카오페이지 웹 구현 중 겪은 일을 각색했으며 해결안 세 가지를 놓고 고른 이유까지 남긴다.
핵심 포인트- 1차 원인: Swiper 의 click 이벤트는 브라우저 click 이 아니라 touchend 시점에 자체 구현된 이벤트다. 종류가 다르니 브라우저 click 의 전파를 막아도 소용이 없다.
- 내부 요소에 직접 리스너를 붙여 둘 다 진짜 click 으로 맞췄는데도 여전히 실패했다. 로그를 찍으니 `onSwiperClick` 이 **먼저** 찍히고 버튼의 `onClick` 이 나중에 찍혔다.
- 2차 원인: React 는 합성 이벤트(SyntheticEvent)를 쓰기 위해 리스너를 **React 루트 요소**에 붙인다. 실제로 그려진 요소에 붙이는 게 아니다. (React 17 부터, 그전에는 document 였다.)
- 그래서 버블 단계에서 React 루트에 붙은 버튼 리스너가 먼저 돌고, 실제 DOM 요소에 붙은 Swiper 리스너가 나중에 돈다. 전파를 막을 타이밍이 이미 지난 것이다.
- 최종 선택은 3안, Swiper 를 감싸는 React Element 를 만들어 거기에 `onClick` 을 붙이는 방법이다.
상세 정리- 상황 전개: 좌우 스와이프 뷰를 Swiper 와 Swiper React Components 로 빠르게 만들고, "슬라이드 아무 데나 클릭하면 다음으로" 요구에 click 리스너를 추가한다.
- 문제 발생: 슬라이드 안에 버튼을 넣자 버튼을 눌러도 Swiper 의 click 이 함께 동작해 다음 슬라이드로 넘어간다.
- `stopPropagation` 의 정의를 다시 짚는다. 발생된 그 이벤트의 전파를 막는 것이므로 상·하위가 같은 click 이벤트여야 한다.
- 근거 확인: Swiper 문서에 "click 이벤트는 arguments 로 touchend 이벤트를 받는다" 고 적혀 있고, 내부 코드를 열어 보니 touchend 때 자체 click 을 발생시키고 있었다.
- 배경: Swiper 는 바닐라 자바스크립트 라이브러리이고 React Components 는 그것을 컴포넌트·훅으로 단순 래핑한 것이라, React Element 의 기본 `onClick` 이 노출되지 않는다.
- 표준 DOM 이벤트의 전파 3단계(캡처·타겟·버블)를 놓고, 타겟 단계의 실제 구현은 캡처나 버블에서 실행되므로 두 단계 순서만 생각하면 된다고 정리한다.
- 실행 순서 재구성: 캡처는 위에서 아래, 버블은 아래에서 위로 진행하며, 버블 단계에서 React 루트 → 상위 React Element → 하위 React Element → 실제 렌더링된 상위 요소 → 하위 요소 순으로 리스너가 돈다.
- 이 순서에서 버튼의 `onClick` 은 React 루트 쪽에, `onSwiperClick` 은 실제 렌더링된 요소 쪽에 붙어 있어 순서가 뒤집혔다.
- 1안 캡처 단계 사용: 자바스크립트 리스너가 기본적으로 버블 단계에서 도는 이유는 이벤트 위임이 뷰 구조와 닮아 직관적이기 때문이다. 이를 거스르면 구조가 복잡해졌을 때 예상 못 한 동작을 만들 수 있어 접었다.
- 2안 렌더링된 button 요소에 직접 붙이기: 잘 동작하지만 하위 요소가 렌더된 뒤에야 붙일 수 있고, 왜 직접 붙였는지 주석으로 남기지 않으면 나중에 같은 과정을 반복하게 된다.
- 3안 채택 이유: React Element 하나가 더 생겨 스타일에 영향을 줄 수 있지만, React 로 계속 만들고 유지보수한다면 간단하고 직관적인 방법이 장기적으로 비용을 줄인다고 판단했다.
- 맺음말로 React 와 다른 라이브러리를 함께 쓸 때, 특히 그 라이브러리가 이벤트를 직접 다룰 때 조심하라고 정리한다.
왜 읽나`stopPropagation` 이 왜 안 먹히는지 설명 못 하는 React 개발자에게 이벤트 전파 단계와 React 의 리스너 부착 위치를 실제 사고로 연결해 준다.