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 로직을 기본으로 작성하는 습관을 들여야겠다.

React Hooks useEffect의 cleanup 함수를 제대로 이해하지 못해 생긴 메모리 누수