React Hooks useEffect의 cleanup 함수를 제대로 이해하지 못해 생긴 메모리 누수
문제 상황
프로덕션에서 간헐적으로 'Can't perform a React state update on an unmounted component' 경고가 발생했다. 사용자 프로필 페이지에서 API 호출 중 다른 페이지로 이동할 때 나타났다.
원인 파악
문제가 된 코드는 다음과 같았다.
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(data => {
setUser(data); // 컴포넌트가 이미 언마운트되었을 수 있음
});
}, [userId]);
return <div>{user?.name}</div>;
}
API 응답이 오기 전에 컴포넌트가 언마운트되면 setState가 호출되면서 메모리 누수가 발생했다.
해결 방법
cleanup 함수에서 flag를 사용해 마운트 상태를 추적하도록 수정했다.
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let isMounted = true;
fetchUser(userId).then(data => {
if (isMounted) {
setUser(data);
}
});
return () => {
isMounted = false;
};
}, [userId]);
return <div>{user?.name}</div>;
}
교훈
Hooks를 도입한 지 얼마 안 돼서 cleanup 함수의 중요성을 간과했다. Class 컴포넌트의 componentWillUnmount를 대체하는 개념이지만, 더 자주 실행된다는 점을 명심해야 한다. deps 배열이 바뀔 때마다 cleanup이 먼저 실행되고 effect가 다시 실행되기 때문이다.
비동기 작업을 다루는 모든 useEffect에는 cleanup 로직을 기본으로 작성하는 습관을 들여야겠다.