문제 상황
모달의 배경을 누르면 모달이 닫히는 기능은 이벤트 버블링을 이용하여 쉽게 구현할 수 있었습니다.
하지만 모달 안에서 Select
컴포넌트의 드롭다운 후 외부를 클릭했을 닫히도록 동일하게 구현하였더니, 드롭다운이 닫히지 않았습니다.
문제 분석
예상되는 원인은 모달 내부에서 e.stopPropagation()
을 이미 사용하고 있었기 때문이었습니다.
모달 내부에서 e.stopPropagation()
을 호출하면서 이벤트의 전파가 막힌 것으로 예상은 되었지만, 어떤 식의 흐름으로 막혔는지는 설명하기 어려웠습니다.
그래서 이벤트 전파에 대해 확실하게 파악하고 싶었습니다.
이벤트 전파 (이벤트 버블링&캡쳐링)
이벤트 흐름은 다음과 같습니다.
- 최상위 요소부터 타겟 요소까지 이벤트가 전파 된 후 (이벤트 캡쳐링)
- 타겟요소부터 이벤트가 전파되어 최상위 요소까지 올라갑니다. (이벤트 버블링)

이벤트 발생 흐름 파악하기
span 태그를 클릭했을 때 이벤트 발생 흐름을 파악해 봅시다.

- span태그에 클릭을 하면, html 최상위 요소부터 자식 태그까지 이벤트가 타고 올라갑니다. (캡쳐링)
- span요소의 이벤트가 실행되면 반대로 부모요소를 타고 내려가며 전파됩니다. (버블링)
자바스크립트 addEventListener()는 기본적으로 버블링으로 동작

addEventListener에 세번째 파라미터로 true를 넣어주면 캡쳐링으로 동작

- 세번째 파라미터는 디폴트는 false로 버블링으로 되어있습니다.
- 세번째 파라미터에 ture를 넣어주면 캡쳐링으로 동작합니다.
Event.eventPhase로 이벤트 흐름 단계 파악

- 부모에서 자식까지 내려갈때 ( 캡쳐링될 때 )
1
- 현재 타겟에 도착했을 때
2
- 타겟에서 부모까지 올라갈때 ( 버블링 될 때 )
3
stopPropagation과 preventDefault의 차이점
- stopPropagation(): 이벤트 전파를 막음. 즉, 이벤트가 부모나 다른 핸들러로 전달되지 않습니다..
- preventDefault(): 기본 동작을 막음. 예를 들어, 링크 클릭 시 페이지 이동을 막거나, 폼 제출 시 페이지 리로드가 방지됩니다.
원인 파악
- 모달 배경을 클릭하면 닫히는 로직이 있음.
- Select 드롭다운이 열렸을 때, 드롭다운 바깥을 클릭하면 닫히는 로직이 있음.
- 근데, 둘 다 동시에 있으니까 문제가 발생
- Select 컴포넌트 드롭다운 바깥을 클릭했을 때, 모달 클릭 이벤트가 먼저 실행됨
- 모달의 e.stopPropagation(); 때문에 Select의 클릭 이벤트 로직이 실행되기 전에 막혀버림.
해결 방법
-
stopPropagation()을 최소화, Select에서만 사용하기
stopPropagation()를중복으로 사용하지 않게 하여 이벤트 흐름 방해를 최소화합니다.
-
select에 캡쳐링 사용하기
Select 컴포넌트에서
document.addEventListener("click", handleClickOutside, true)
를 추가하면 캡쳐링으로 동작합니다. 이렇게 하면 Select의 로직이 제일 먼저 실행됩니다.
결론
이번 버그를 통해 이벤트의 흐름과 제어하는 방식에 대해서 깊게 생각해볼 수 있었습니다. 첫 번째 방법인 stopPropagation() 을 중복으로 사용하지 않는 방법도 가능했지만, 캡처링을 활용한 이벤트 처리를 통해 근본적은 원인을 파악하고 문제를 해결할 수 있었습니다!