2025. 4. 17. 12:40ㆍFrontend
도입
리액트는 가상 DOM을 사용해요.
이 가상 DOM은 리액트의 메모리 안에 저장돼 있죠.
그렇다면 이 메모리는 어떻게 관리할 수 있을까요?
이번 글에서는 리액트 메모리 누수를 막기 위한 관리 방법을 알아보려고 해요.
리액트와 메모리
리액트는 자바스크립트(JS) 기반 라이브러리예요.
그래서 리액트 메모리는 자바스크립트 메모리 하고 관련 있어요
자바스크립트는 고급 언어로,
직접 메모리를 해제하지 않아도 자동으로 메모리를 관리해 줘요.
이 역할을 하는 게 가비지 컬렉터(Garbage Collector)예요.
가비지 컬렉터는 참조되지 않는 객체를 쓰레기로 판단해서
이를 메모리에서 제거해 공간을 확보해 줘요.
이 정리 작업은 일정 주기마다 일어나는 게 아니라,
엔진이 필요하다고 판단할 때 자동으로 동작해요.
자바스크립트 메모리는 어떻게 정리될까?
메모리는 할당 → 사용 → 해제 순으로 생명 주기를 가져요.
할당
// 할당
let date = new Date();
변수를 선언하면 메모리를 확보해요.
사용
// 사용
console.log(date);
그 값을 사용할 땐 해당 메모리를 읽어요.
해제
// 해제 (GC가 자동 처리)
date = null;
더 이상 참조하지 않게 되면 가비지 컬렉터가 해제해요.
저급 언어에서는 직접 메모리 해제를 명시해야 하지만,
자바스크립트에서는 가비지 컬렉터가 자동으로 메모리 해제를 해줘요.
리액트 메모리는 어떻게 구성되어 있나요?
메모리는 크게 스택(stack)과 힙(heap) 구조로 나뉘어요.
함수는 스택에서 실행되고, 객체는 힙에 저장돼요.
함수는 객체의 한 종류이고
리액트 함수 컴포넌트에서 컴포넌트는 함수예요.
컴포넌트 내부에서 생성된 값이나
클로저는 힙에서 관리되기 때문에 참조가 남을 수 있어요.
클로저
: 외부 함수의 환경을 참조할 수 있는 내부 함수
한 가지 예제를 한 번 살펴볼게요.
function App() {
return <div>Hi</div>;
}
App 컴포넌트는 실행될 때 스택에서 호출되고,
이 안에서 생성된 객체는 힙에 저장돼요.
만약 App 컴포넌트가 언마운트되면,
더 이상 참조되지 않기 때문에
가비지 컬렉터가 App 컴포넌트 내에서
참조하지 않는 상태나 객체를 메모리에서 제거해요.
이렇게 메모리를 자동으로 관리해 주면 좋지만
그렇지 않은 경우가 있어요.
리액트 메모리 관리 할 때 주의할 점
리액트에서는 데이터를 불러오거나,
이벤트를 등록하고, 타이머를 설정하는 일이 많아요.
문제는 이런 비동기 동작이나 이벤트 리스너가
자동으로 제거되지 않는다는 점이에요.
자바스크립트는 참조가 남아 있으면
해당 객체를 제거하지 않기 때문이에요.
그래서 리액트에서는
이벤트 리스너나 타이머를 직접 해제해 주는 작업이 꼭 필요해요.
이 작업을 클린업(clean-up)이라고 불러요.
클린업 예시
function App() {
useEffect(() => {
const sayHi = () => console.log('Hi');
setInterval(sayHi, 1000);
}, []);
return <></>
}
위 코드는 "Hi"를 1초마다 출력해요.
하지만 App 컴포넌트는 언마운트되어도,
setInterval의 콜백 함수가 스케줄러에 저장되어 있어서
"Hi"를 계속 출력해요.
타이머를 멈추지 않으면,
App 컴포넌트가 마운트 될 때마다
새로운 타이머가 계속 추가돼요.
마운트가 한 번 더 일어나면
"Hi"가 두 번씩 출력되죠.
이게 리액트에서 자주 발생하는 메모리 누수 사례예요.
이러한 현상은 클린업 함수로 해결할 수 있어요.
useEffect(() => {
const sayHi = () => console.log('Hi');
const interval = setInterval(sayHi, 1000);
// useEffect 반환 함수 작성
return () => clearInterval(interval);
}, []);
useEffect에서 반환 함수는
컴포넌트가 언마운트될 때 실행돼요.
그래서 clearInterval을 사용하면
이전에 등록한 타이머를 중지할 수 있어요.
타이머를 중지하면, 콜백 함수도 더 이상 참조되지 않아서
가비지 컬렉터가 정리할 수 있어요.
그럼,
컴포넌트가 다시 마운트 되어도
"Hi"는 1초마다 한 번만 출력돼요.
정리
리액트는 자바스크립트 위에서 동작하기 때문에
가비지 컬렉터에 의해 메모리는 자동으로 정리돼요.
하지만 타이머, 이벤트, 구독 등 비동기 동작은 직접 정리해야 해요.
이 정리 과정을 클린업(clean-up)이라고 부르고,
대부분 useEffect의 반환 함수에서 처리해요.
메모리는 눈에 보이지 않지만,
쌓이고 나면 성능 저하로 이어질 수 있어요!
같이 보기
Event loop의 동작과정 - 스택, 힙, 큐 (wikidocs)
자바스크립트 mark-and-sweep 방식 가비지 컬렉션 설명 (javascript.info)
참고
1) 자바스크립트 메모리 관리 방식 및 흔한 메모리 누수 사례 정리 (Zlatkov)
2) JS 메모리 관리 기초 및 GC 원리 (Felix Gerschau)
3) removeChild 되어도 메모리 누수 발생 사례 (owenjeon)
4) useEffect의 생명 주기, 마운트/업데이트/언마운트 타이밍 정리 (React 공식 문서)
5) 가비지 컬렉션과 setInterval, setTimeout (javascript.info)
'Frontend' 카테고리의 다른 글
왜 자바스크립트 객체는 값이 같아도 다르다고 할까? (0) | 2025.04.23 |
---|---|
원시값 vs 객체 – useMemo와 리렌더링의 관계 (1) | 2025.04.22 |
리액트만의 가상 DOM 렌더링 알고리즘 살펴보기 (0) | 2025.04.15 |
리액트는 항상 useEffect를 필요로 하지 않아요. (0) | 2025.04.11 |
매번 달라지던 하늘, 결국 리렌더링 문제였어요 (0) | 2025.04.06 |