화면을 옮겼는데 이전 화면의 요청 결과가 늦게 도착해 새 화면을 덮어쓰거나, 창 크기 변경 이벤트가 여러 번 실행된다면 effect가 만든 작업을 정리하지 않았을 수 있다. useEffect의 cleanup은 다음 effect 실행 전과 컴포넌트가 사라질 때 호출된다.
이벤트 리스너는 같은 함수로 해제한다
도식처럼 effect에서 등록한 구독은 같은 effect가 반환하는 cleanup에서 해제해야 짝이 맞는다. 창 크기 이벤트를 등록하고 제거하는 예제로 확인해 보자.
import { useEffect, useState } from "react";
function WindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
function onResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
return <p>창 너비: {width}px</p>;
}창 크기를 바꾸면 숫자가 갱신된다. 컴포넌트를 화면에서 제거하면 리스너도 해제된다. 등록과 해제에 서로 다른 익명 함수를 쓰면 제거되지 않는다. effect가 외부 시스템에 연결했다면 cleanup은 그 연결을 끊는 짝으로 생각하면 된다. 개발 모드의 Strict Mode에서 등록·해제가 한 번 더 보일 수 있으나, 리스너가 누적되지 않는지가 핵심이다.
요청은 오래된 응답을 취소하거나 무시한다
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [error, setError] = useState(false);
useEffect(() => {
const controller = new AbortController();
setError(false);
fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
.then((response) => {
if (!response.ok) throw new Error("검색 실패");
return response.json();
})
.then(setResults)
.catch((reason) => {
if (reason.name !== "AbortError") setError(true);
});
return () => controller.abort();
}, [query]);
if (error) return <p>검색을 다시 시도해 주세요.</p>;
return <p>결과 {results.length}건</p>;
}검색어가 바뀌면 이전 effect의 cleanup이 먼저 실행되어 이전 요청을 취소한다. /api/search는 앱에 실제 검색 API가 있을 때 연결할 경로다. 검색어를 연달아 바꾸고 오래된 응답이 새 결과를 덮지 않는지 확인하자. 모든 API가 취소를 즉시 처리하는 것은 아니므로, 결과를 반영할 때 현재 요청인지 확인하는 전략이 필요한 경우도 있다.
dependency 배열은 데이터 흐름의 목록이다
effect 안에서 읽는 props·state를 dependency에서 빼면 오래된 값을 캡처할 수 있다. 반대로 매 렌더마다 새로 만들어지는 객체·함수를 의존성에 넣으면 effect가 불필요하게 반복된다. 경고를 끄기보다 값이 왜 바뀌고 effect가 언제 다시 실행돼야 하는지 먼저 정리하자.
cleanup 호출 횟수를 확인한다
기존 resize 예제에서 화면을 진입·이탈한 뒤 창 크기를 바꿔 본다. 이탈 후에도 onResize가 실행되면 등록한 함수와 해제한 함수가 같은 참조인지 확인한다. React 개발 모드에서 Effect가 다시 실행되어도 등록 → 해제 → 등록 순서가 유지돼야 한다.
검색 요청 예제는 빠르게 query를 두 번 바꿔 오래된 요청이 취소되는지 점검한다. 취소로 발생한 AbortError는 사용자 오류로 표시하지 않는다. 실제 서버의 응답 본문이나 내부 주소를 화면·로그에 그대로 싣지 않는 것도 함께 확인한다.
핵심 요약
cleanup은 effect가 만든 리스너·타이머·구독·요청을 다음 실행이나 언마운트 전에 정리한다. 외부 연결을 만들었다면 해제 코드를 같은 effect에서 반환하자. 의존성 배열은 실행 시점을 설명하는 계약이므로 누락과 불안정한 값을 모두 점검해야 한다.
작성자
기초 개념을 구현과 검증, 실제 운영 판단까지 연결해 기록합니다.

