React 17 업그레이드 후 이벤트 위임 변경 이슈
문제 상황
회사 프로젝트를 React 17로 업그레이드한 후, 레거시 코드로 작성된 document 레벨 이벤트 리스너가 제대로 동작하지 않는 이슈가 발생했다. 특히 모달 외부 클릭으로 닫기 기능이 작동하지 않았다.
원인 분석
React 17부터 이벤트 위임 지점이 document에서 React 트리가 렌더링되는 root DOM 컨테이너로 변경되었다. 기존에는 모든 React 이벤트가 document에 등록되었지만, 이제는 ReactDOM.render가 호출된 DOM 노드에 등록된다.
// 기존 레거시 코드
document.addEventListener('click', (e) => {
if (!modalRef.current.contains(e.target)) {
closeModal();
}
});
이 코드는 React 16까지는 잘 동작했지만, 17에서는 이벤트 전파 순서가 달라져 e.stopPropagation()이 document까지 전파를 막지 못하는 경우가 생겼다.
해결 방법
이벤트 리스너를 root 컨테이너보다 상위에 등록하거나, React의 이벤트 시스템을 사용하도록 리팩토링했다.
// 해결 1: useEffect로 React 방식 사용
useEffect(() => {
const handleClickOutside = (e) => {
if (modalRef.current && !modalRef.current.contains(e.target)) {
closeModal();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
// 해결 2: 포털 사용
ReactDOM.createPortal(
<Modal onClose={closeModal} />,
document.body
);
결론
React 17의 이벤트 시스템 변경은 micro-frontend나 다른 라이브러리와의 통합을 위한 개선이다. 레거시 코드가 많다면 업그레이드 전에 이벤트 핸들링 로직을 점검할 필요가 있다. 다행히 큰 breaking change는 없어서 하루 만에 마이그레이션을 완료했다.