클린업(Cleanup) 함수의 발견

252025년 09월 09일4

useEffect의 새로운 설계는 팀에 큰 흥분을 안겨주었다. 구독을 시작하는 코드와 해지하는 코드가 하나의 블록 안에 공존하는 것은, 클래스 시절에는 상상할 수 없었던 우아함이었다.

댄은 이 ‘반환된 함수’의 개념에 깊이 매료되었다. 그는 이것이 단순히 componentWillUnmount의 대체재가 아니라는 것을 직감했다. 이것은 리액트가 부수 효과를 다루는 방식을 근본적으로 바꾸는, 더 깊은 의미를 내포하고 있었다.

그는 동료들에게 자신의 생각을 설명하기 위해 간단한 예제를 작성했다.

function Timer() {
  const [time, setTime] = useState(0);

  useEffect(() => {
    // 1초마다 time 상태를 1씩 증가시키는 타이머를 설정한다.
    const timerId = setInterval(() => {
      setTime(t => t + 1);
    }, 1000);

    console.log("타이머가 설정되었습니다. ID:", timerId);

    // 컴포넌트가 사라질 때 타이머를 정리(cleanup)한다.
    return () => {
      console.log("타이머를 해제합니다. ID:", timerId);
      clearInterval(timerId);
    };
  });

  return <p>Timer: {time} seconds</p>;
}

Timer 컴포넌트가 화면에 나타나면, useEffect가 실행되어 setInterval이 설정되고, 콘솔에는 ‘타이머가 설정되었습니다’라는 로그가 찍혔다. 그리고 컴포넌트가 화면에서 사라지면, useEffect가 반환했던 함수가 실행되어 clearInterval이 호출되고, ‘타이머를 해제합니다’라는 로그가 찍혔다.

여기까지는 componentDidMountcomponentWillUnmount의 동작과 정확히 일치했다.

“여기서 중요한 점은,”
댄이 말을 이었다.
“클린업 함수가 timerId라는 변수에 접근할 수 있다는 것입니다.”

클래스 컴포넌트에서는 timerIdthis.timerId와 같이 컴포넌트 인스턴스에 저장해야만 componentWillUnmount에서 접근할 수 있었다. 하지만 useEffect 모델에서는 그럴 필요가 없었다. 클린업 함수는 자신이 선언된 useEffect의 스코프(Scope) 안에 존재했기 때문에, 자바스크립트의 클로저(Closure) 원리에 따라 바깥 스코프의 timerId 변수를 자연스럽게 ‘기억’하고 있었다.

개발자는 더 이상 this에 무언가를 저장하고 관리할 필요가 없었다. 필요한 변수를 선언하고, 사용하고, 정리하는 모든 과정이 하나의 함수 블록 안에서 완결되었다.

“이것이 바로 진정한 관심사의 분리입니다.”
소피가 댄의 설명에 덧붙였다.
“타이머를 설정하고 해제하는 모든 로직이 이제 완벽하게 하나의 단위로 캡슐화되었습니다. 이 useEffect 블록 전체를 다른 컴포넌트로 복사-붙여넣기만 해도, 아무 문제 없이 작동할 겁니다.”

로직의 이동성과 재사용성이 비약적으로 향상되는 순간이었다.

세바스티안은 여기서 한 걸음 더 나아갔다.
“이 모델의 진정한 힘은, 클린업이 단지 ‘언마운트(unmount)’ 시에만 일어나는 것이 아니라는 점입니다. 다음 ‘효과(effect)’가 실행되기 전에 항상 이전 효과를 정리해준다는 것이 핵심이죠.”

그의 말은 useEffect가 단순히 생명주기 메서드를 흉내 내는 것을 넘어, 완전히 새로운 동작 모델을 제시하고 있음을 시사했다. 클래스의 생명주기는 ‘시간’에 기반한 불연속적인 이벤트(mount, update, unmount)였다. 하지만 useEffect는 ‘동기화’에 가까웠다.

useEffect는 리액트에게 이렇게 말하는 것과 같았다.
“이 컴포넌트의 상태를, 바깥세상의 상태와 동기화하고 싶어. 동기화를 시작하는 방법은 이것이고, 동기화를 푸는 방법은 저것이야. 리액트, 네가 알아서 필요할 때마다 동기화를 풀고 다시 시작해줘.”

이 ‘정리 후 재실행(cleanup-and-rerun)’ 모델은 이전에는 수많은 조건문과 플래그 변수로 고통스럽게 구현해야 했던 복잡한 로직들을 놀랍도록 단순하게 만들어줄 잠재력을 품고 있었다.

팀은 자신들이 발견한 것이 단순한 API가 아님을 깨달았다.
그것은 리액트 개발의 패러다임을 바꿀, 강력하고도 새로운 멘탈 모델이었다. 클린업 함수의 발견은 그 모델을 완성하는 마지막 열쇠 조각이었다.

이제 남은 과제는 이 강력한 힘을 어떻게 제어할 것인가의 문제였다. 모든 렌더링마다 효과를 정리하고 다시 실행하는 것은 어떤 경우에는 심각한 비효율을 낳을 수 있었기 때문이다. 그들에게는 이 강력한 엔진의 속도를 조절할 브레이크가 필요했다.