커스텀 명언 카드를 생성하는 화면에서 리렌더링이 반복될 때마다 미묘한 버벅임 현상이 발생하였고, 이로 인해 사용자 경험이 저하되고 있었다. 브라우저 개발자 도구의 성능 탭과 메모리 탭을 통해 분석한 결과, 시간 경과에 따라 일부 메모리가 해제되지 않고 남아 있는 것을 확인하였다.
이러한 현상의 근본적인 원인은 컴포넌트가 언마운트될 때 등록된 이벤트 리스너가 정상적으로 제거되지 않아, 리렌더링 시마다 이벤트가 중첩 등록되며 메모리가 누적되는 데 있었다.
⇒ 즉, 이벤트 리스너 등록과 해제를 동일한 참조로 처리하지 않아, 이전에 등록된 리스너가 해제되지 못하고 계속 남아 있게 되는 구조였다.
처음에는 스택오버플로우나 MDN 등을 참고하여 유사 사례를 확인하였고, 메모리 누수 가능성을 중심으로 원인을 좁혀갔다. 특히 이미지 로딩 시 addEventListener
로 등록된 로직이 상태 변화마다 중첩되어 등록되고, 해제되지 않는다는 점이 핵심적인 문제로 판단되었다.
해당 문제를 해결하기 위해 useEffect
내부에서 이미지 로딩 이벤트를 등록할 때, 이벤트 리스너 함수를 명확하게 함수로 분리하고, 이 함수를 클린업 함수에서 정확히 동일한 참조로 해제하도록 처리하였다.
draw()
함수 내부에서 이미지가 로드될 때 실행되는 handleImageLoad
함수를 정의하고, 이를 imageEl.addEventListener('load', handleImageLoad)
로 등록한 후, useEffect
의 반환 함수에서 imageEl.removeEventListener('load', handleImageLoad)
로 해제하였다.
이러한 방식으로 컴포넌트가 언마운트되거나 리렌더링 될 때, 불필요하게 누적되던 이벤트 리스너를 제거할 수 있도록 로직을 정비하였다.
이후, 크롬 개발자 도구의 메모리 탭에서 할당 계측 기능을 사용하여 리스너 해제 이후에도 메모리 누수가 여전히 발생하는지 관찰하였으며, 성능 탭에서 ‘강력 캐시 비우기’를 한 후 다양한 사용자 이벤트를 통해 메모리 사용량이 변화하는 양상을 분석하였다.