1. Cleanup 함수의 기본 역할
React를 다루다 보면 useEffect 내부에 return으로 전달하는 cleanup 함수(clean-up function)를 자주 보게 된다. cleanup 함수는 컴포넌트가 언마운트되거나, 의존성 배열(dependency array)에 변화가 생길 때 이전 이펙트를 정리하기 위해 동작한다. 이를 통해 메모리 누수나 불필요한 이벤트 리스너 잔존 문제를 방지한다.
useEffect(() => {
const id = setInterval(() => {
console.log('tick');
}, 1000);
// cleanup 함수
return () => {
clearInterval(id);
console.log('cleanup!');
};
}, []);
예를 들어 setInterval, addEventListener, WebSocket, Subscription 등 외부 리소스를 사용하는 경우, 이를 정리하지 않으면 메모리 누수나 중복 실행 문제가 생길 수 있다.
2. Cleanup 함수의 실행 타이밍
(1) 컴포넌트 언마운트 시
컴포넌트가 화면에서 사라질 때(unmount), cleanup 함수가 한 번 실행된다.
useEffect(() => {
const timer = setInterval(() => console.log('running...'), 1000);
return () => {
clearInterval(timer);
console.log('unmounted - cleanup!');
};
}, []); // 빈 배열
- []는 의존성이 없기 때문에 effect는 최초 마운트 시 한 번 실행한다.
- 컴포넌트가 언마운트될 때 cleanup이 실행된다.
(2) 디펜던시 변경 시
dependency array에 값이 들어 있다면, 값이 바뀌기 직전에 cleanup이 먼저 실행된다.
useEffect(() => {
console.log('effect 실행:', count);
return () => {
console.log('cleanup 실행:', count);
};
}, [count]);
실행 순서 예시는 다음과 같다:
count = 0 → effect 실행: 0
count = 1 → cleanup 실행: 0 → effect 실행: 1
count = 2 → cleanup 실행: 1 → effect 실행: 2
즉, useEffect는 의존성 배열에 있는 값이 바뀌기 전에, 이전 effect에서 생성된 side effect(이전 effect가 남긴 부수작용)을 먼저 정리(cleanup)한다. 이렇게 함으로써 이전 effect와 새 effect가 동시에 영향을 주어 꼬이는 것을 방지하고, side effect가 안전하게 관리되도록 보장한다.
side effect 제거 예시
1. 타이머/Interval
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id); // cleanup
}, [count]);
count가 바뀔 때마다 새 interval이 생성되면 이전 interval이 그대로 남아 중복 실행될 수 있기 때문에 cleanup이 이전 interval을 제거해서 꼬임을 방지한다.
2. 구독/이벤트 리스너
useEffect(() => {
const handleResize = () => console.log(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
컴포넌트가 언마운트되거나 dependency가 바뀔 때 이전 리스너를 제거하지 않으면 메모리 누수나 중복 호출이 발생할 수 있다.
3. API 요청 취소
useEffect(() => {
const controller = new AbortController(); // 취소 토큰 생성
fetch("/api/data", { signal: controller.signal });
return () => controller.abort(); // 이전 요청 취소
}, [query]);
사용자가 빠르게 검색어를 바꾸면 이전 요청이 완료되지 않은 상태에서 새 요청이 발생한다. 이때, cleanup에서 이전 요청을 취소하면, 꼬이는 케이스나 불필요한 네트워크 요청을 막을 수 있다.
3. 주의해야 할 점
1. Cleanup이 없으면?
이벤트 리스너를 추가하는 예시에서 cleanup이 없으면, 이벤트가 계속 추가되며 점점 중복 실행된다.
useEffect(() => {
window.addEventListener('scroll', handleScroll);
}, []);
이를 정리하지 않으면 컴포넌트가 사라져도 이벤트가 남아 있을 수 있다.
따라서, 아래와 같이 명시적으로 제거해줘야 한다.
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
2. 의존성 배열 누락 시
useEffect의 의존성 배열을 잘못 설정하면 cleanup이 제대로 작동하지 않거나, 필요 이상으로 effect가 반복 실행된다.
React의 ESLint 플러그인(eslint-plugin-react-hooks)을 사용하면 문제를 자동으로 감지한다.
# 설치
npm install eslint-plugin-react-hooks --save-dev
// .eslintrc.json 예시
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error", // 훅 사용 규칙
"react-hooks/exhaustive-deps": "warn" // 의존성 배열 체크
}
}
ESLint 경고 예시
React Hook useEffect has missing dependencies: 'count' and 'name'. Either include them or remove the dependency array. (react-hooks/exhaustive-deps)
4. 실무에서의 팁
- Subscription / Timer / EventListener → 반드시 cleanup을 작성한다.
- 단순 API 호출만 하는 경우 cleanup은 필수는 아니지만, 실시간 검색처럼 사용자가 계속 입력을 바꾸며 새로운 쿼리가 발생하는 상황에서는 이전 요청을 취소하기 위해(취소 토큰 관리를 위해) cleanup을 활용할 수 있다.
- React.StrictMode에서는 개발 환경에서 effect가 두 번 실행되므로 cleanup이 정확히 되어 있는지 검증할 수 있다.
React.StrictMode 에서의 문제 발생 케이스: cleanup 누락
import { useEffect, useState } from 'react';
function ScrollTracker() {
const [scroll, setScroll] = useState(0);
const handleScroll = () => {
setScroll(window.scrollY);
console.log('스크롤 이벤트 실행');
};
useEffect(() => {
// cleanup을 작성하지 않음
window.addEventListener('scroll', handleScroll);
}, []);
return <div>현재 스크롤 위치: {scroll}</div>;
}
스크롤 이벤트 실행
스크롤 이벤트 실행
- StrictMode에서 effect가 두 번 실행되기 때문에, 이벤트 리스너가 두 번 등록됨
- 결과적으로 스크롤을 한 번 움직여도 handler가 두 번 호출됨
- 실제 배포 환경에서는 한 번만 실행되지만, 개발 중에는 중복 문제를 발견할 수 있도록 도와준다.
5. 요약 및 마무리
| 상황 | cleanup 실행 시점 |
| [] (빈 배열) | 언마운트 시 한 번 |
| [deps] (의존성 있음) | deps 변경 시마다, 다음 effect 실행 전에 |
| 생략 시 | cleanup 없음 — 메모리 누수 가능성 ↑ |
| StrictMode | 개발환경에서 두 번 호출되어 정상 동작 여부 테스트 |
cleanup 함수는 “효과를 없애는 함수”가 아니라, 이전 상태를 정리하고 새로운 상태를 준비하는 과정이다. React는 이를 통해 side effect를 예측 가능한 순서로 관리한다.
'Frontend > React' 카테고리의 다른 글
| 상태(stateful) & 비상태(stateless) 컴포넌트 (0) | 2025.10.25 |
|---|---|
| React에서 비동기 처리 최적화하기 (0) | 2025.10.25 |
| useRef 자세히 알아보기 (0) | 2025.10.24 |
| React 메모이제이션과 최적화 (0) | 2025.10.24 |
| Virtual DOM (0) | 2025.10.16 |