React에서 비동기 취소와 메모리 누수 예방
React의 비동기 작업에서 발생하는 상태 업데이트와 메모리 누수를 정리하고, useEffect 내에서의 취소 패턴과 AbortController 적용, isMounted 검사 등 실무적 방지 전략
목차
개요
React 애플리케이션에서 비동기 호출이 컴포넌트 수명과 충돌하면서 메모리 누수나 불필요한 상태 업데이트가 발생한다. 특히 네트워크 지연이나 라우트 변경 시 이전 요청이 완료된 뒤 setState가 호출되면 경고와 버그가 발생한다. 이 글은 react async cancel useEffect 관점에서 흔한 문제와 현실적인 해결법을 정리한다.
문제 이해
어떤 상황에서 누수가 생기는가
컴포넌트가 언마운트된 뒤 비동기 함수가 완료되어 setState를 호출하면 경고가 발생한다. 예를 들어 데이터 패칭이 오래 걸릴 때 사용자가 다른 페이지로 이동하면 이전 컴포넌트의 상태 업데이트가 여전히 남아있을 수 있다. 이로 인해 메모리 사용량이 불필요하게 증가하거나 예측하지 못한 동작이 발생한다.
왜 취소가 필요한가
비동기 작업을 명시적으로 취소하면 불필요한 연산과 상태 변경을 막을 수 있다. 네트워크 요청을 취소하면 브라우저와 서버 자원을 아낄 수 있고, 컴포넌트의 일관성을 유지할 수 있다. 따라서 react async cancel useEffect 같은 패턴이 중요하다.
패턴 1 — AbortController 사용
개념
AbortController는 fetch 같은 API 호출을 취소하도록 설계된 표준 Web API다. useEffect 내부에서 컨트롤러를 생성하고 cleanup 시점에 abort를 호출하면 요청이 중단된다. 코드가 간결하고 네이티브 지원을 활용한다.
abortcontroller react 예제
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
async function fetchData() {
try {
const res = await fetch('/api/data', { signal });
if (!res.ok) throw new Error('Network response not ok');
const data = await res.json();
setData(data);
} catch (err) {
if (err.name === 'AbortError') {
// 요청이 취소됨
} else {
setError(err);
}
}
}
fetchData();
return () => controller.abort();
}, []);
위 패턴은 fetch 기반 호출에 즉시 적용된다. axios 같은 라이브러리도 자체 취소 토큰을 제공하므로 라이브러리 문서에 맞춰 사용하면 된다.
패턴 2 — isMounted 플래그 검사
개념
모든 비동기 API가 취소를 지원하는 것은 아니다. 이때는 useEffect 내부에서 플래그를 두고 언마운트 시 플래그를 변경하여 이후 setState 호출을 차단한다. 간단하지만 올바르게 사용하면 안전하다.
useEffect(() => {
let mounted = true;
async function load() {
try {
const res = await fetch('/api/data');
const json = await res.json();
if (!mounted) return;
setData(json);
} catch (err) {
if (!mounted) return;
setError(err);
}
}
load();
return () => { mounted = false; };
}, []);
이 방식은 단순하고 안정적이다. 다만 요청 자체가 계속 실행되므로 네트워크/서버 비용은 그대로 발생한다는 점에 유의한다.
실무 권장 흐름
- 가능하면 AbortController를 우선 사용한다. 네이티브로 요청을 중단할 수 있어 자원 낭비를 줄인다.
- 외부 라이브러리를 사용할 경우 해당 라이브러리의 취소 메커니즘을 확인한다. axios, ky 등은 자체 취소 API를 제공한다.
- 취소가 불가능한 작업은 isMounted 플래그로 보호한다. UI 업데이트를 안전하게 차단하는 용도에 적합하다.
- 여러 요청을 병렬로 실행할 때는 각각의 컨트롤러를 따로 관리하거나 공통 컨트롤러를 설계한다.
추가 고려사항
에러 처리와 사용자 경험
취소는 에러와 다르게 다뤄져야 한다. AbortError는 정상 흐름으로 간주하고 별도 메시지를 노출하지 않는 편이 낫다. 사용자는 네트워크 상태나 라우트 이동으로 인해 요청이 중단되었다는 것을 알 필요가 없는 경우가 많다.
테스트와 디버깅
의도한 대로 cleanup이 실행되는지 확인하려면 개발자 도구에서 네트워크 탭과 콘솔 로그를 함께 확인한다. 컴포넌트가 빠르게 마운트·언마운트되는 경로를 시뮬레이션하면 누수 가능 지점을 찾기 쉽다.
체크리스트
- useEffect 내부에서 비동기 호출을 수행하는가
- 네이티브 취소 API(AbortController)를 활용 가능한가
- 라이브러리의 취소 메커니즘을 확인했는가
- 언마운트 이후 setState 호출을 방지하는 보호 장치가 있는가
- 불필요한 재요청을 막기 위한 의존성 관리를 했는가
마무리
리액트에서 비동기 취소는 안정성과 자원 효율을 높이는 기본 수단이다. 위에서 설명한 패턴들을 상황에 맞게 조합하면 리액트 메모리 누수 방지와 예측 가능한 UI 상태 관리가 가능하다. 특히 react async cancel useEffect 패턴과 abortcontroller react 예제는 실무에 바로 적용 가능한 방법이다. 작은 실천이 누적되어 애플리케이션 품질을 크게 개선한다.