React useEffect 무한 루프 에러와 렌더링 최적화 아키텍처

React useEffect 무한 루프 에러와 렌더링 최적화 아키텍처

"API 서버에서 데이터를 받아와 화면에 뿌리는 간단한 코드를 짰는데, 갑자기 API 호출이 1초에 수백 번씩 일어나면서 크롬 브라우저가 완전히 뻗어버렸습니다."

리액트(React)를 다루는 프론트엔드 개발자들이 반드시 한 번 이상 겪게 되는 무서운 버그입니다. 브라우저가 멈추는 선에서 끝나면 다행이지만, 이 코드가 프로덕션 서버에 그대로 배포되면 AWS 트래픽 요금 폭탄이나 백엔드 데이터베이스 다운이라는 재앙으로 직결됩니다.

이 현상은 useEffect 무한 루프라 불립니다. 초보자들은 의존성 배열(Dependency Array)에 노란색 경고가 뜨면 무작정 아무 변수나 집어넣어 에러 표시만 끄려 합니다. 본 가이드에서는 리액트의 '렌더링 생명주기(Lifecycle)'라는 근본적 메커니즘을 해체하고, 무한 루프를 원천 차단하는 상태 관리 아키텍처를 단호하게 제시합니다.

📌 이 글의 핵심 포인트

  • 무한 루프의 악순환: State 업데이트가 렌더링을 부르고, 렌더링이 다시 Effect를 실행하는 구조
  • 의존성 배열(Dependency Array) []의 정확한 이해와 활용법
  • 렌더링과 무관한 상태 관리를 위한 useRef 아키텍처 도입

1단계: 에러의 본질 - 렌더링 지옥의 굴레

React 컴포넌트는 자신이 들고 있는 상태(State)가 변경될 때마다 함수를 처음부터 끝까지 다시 실행하여 화면을 새로 그립니다(Re-render). 그런데 데이터를 받아오기 위해 useEffect 내부에서 setState를 호출하면 어떤 일이 벌어질까요?

  1. 컴포넌트 마운트
  2. useEffect 실행
  3. 서버에서 데이터 수신
  4. setState 업데이트
  5. State가 변했으니 컴포넌트 다시 렌더링
  6. 다시 렌더링 되었으니 useEffect 또 실행
  7. setState 또 업데이트
  8. (무한 반복)

이 굴레가 빛의 속도로 반복되며 브라우저의 메모리와 CPU를 완벽하게 집어삼키는 것입니다.

2단계: 완벽한 트러블슈팅 - 의존성 배열의 강력한 통제

이 끔찍한 악순환을 끊는 가장 확실하고 유일한 방법은 useEffect의 두 번째 인자인 배열(Array)을 철저하게 통제하는 것입니다.


// ❌ 최악의 코드 (배열 생략): 컴포넌트가 렌더링될 때마다 무조건 다시 실행됨
useEffect(() => {
    fetchDataAndSetState();
});

// ✅ 최초 1회 실행 보장: 컴포넌트가 화면에 '처음' 나타날 때 딱 한 번만 실행됨
useEffect(() => {
    fetchDataAndSetState();
}, []); // 텅 빈 배열을 넣어야 함

// ✅ 조건부 실행: userId 값이 변경되었을 때만 Effect를 재실행함
useEffect(() => {
    fetchUserData(userId);
}, [userId]); 

화면이 켜질 때 한 번만 데이터를 가져오고 싶다면 반드시 빈 배열 []을 넣어야 합니다. 배열을 아예 생략하는 것은 컴포넌트의 통제권을 포기하는 행위입니다.

3단계: 심층 아키텍처 - useRef를 활용한 은밀한 저장소

종종 배열 안에 특정 상태값을 꼭 넣어야 하는데, 넣기만 하면 무한 루프가 도는 딜레마에 빠질 때가 있습니다. 이럴 때는 리액트의 렌더링 사이클에서 완벽하게 벗어난 데이터 금고가 필요합니다.

화면에 당장 그려서 보여줘야 할 데이터가 아니라, 백그라운드에서 값만 기억하고 있어야 할 데이터라면 useState 대신 useRef를 사용하십시오. useRef에 저장된 .current 값은 아무리 변하고 수정되어도 리액트 컴포넌트를 절대 재렌더링 시키지 않으므로, 무한 루프의 고리를 완벽하게 끊어낼 수 있습니다.

🙋‍♂️ 자주 묻는 질문 (FAQ)

Q. 빈 배열([])을 넣었는데도 API가 2번 연속으로 호출됩니다. 버그인가요?
A. 아닙니다. React 18 버전부터 Strict Mode(엄격 모드)가 켜진 개발 환경에서는 개발자의 사이드 이펙트 실수를 잡아내기 위해 컴포넌트를 강제로 2번씩 마운트시킵니다. 실제 프로덕션 빌드 후 배포하면 정상적으로 1번만 호출됩니다.

Q. 객체나 배열을 의존성 배열에 넣었더니 계속 무한 루프가 돕니다.
A. 자바스크립트는 객체나 배열이 렌더링될 때마다 '새로운 메모리 주소'를 할당받습니다. 안의 내용물이 같아도 주소가 다르니 리액트는 "값이 변했다!"고 착각하여 Effect를 다시 실행합니다. useMemo를 사용해 객체의 주소값을 고정(메모이제이션)시켜야 합니다.

💡 핵심 정리 및 마무리

React는 단순히 DOM을 조작해 주는 라이브러리가 아닙니다. 데이터 상태(State)의 흐름에 따라 UI를 동기화(Synchronize)시키는 거대한 시스템입니다. useEffect는 이 시스템 외부(네트워크, 타이머 등)와 소통하기 위한 위험한 통로입니다. 렌더링의 주도권을 잃지 말고 의존성 배열을 완벽하게 통제하여 메모리 누수 없는 견고한 프론트엔드를 구축하십시오.

댓글

이 블로그의 인기 게시물

Zapier & Make.com 자동화의 덫: 무한 루프(Infinite Loop) 에러 완벽 방어 아키텍처

엑셀 보고서의 종말: 구글 스프레드시트와 루커 스튜디오(Looker Studio)로 실시간 대시보드 구축하기

Docker OOMKilled (Exit Code 137) 에러의 진실: 컨테이너 메모 누수 방어 및 리소스 최적화 아키텍처