TIL - 20250922

juni·2025년 9월 22일

TIL

목록 보기
133/315

0922 React Performance Tuning: Solving Infinite Renders


✅ 1. 문제 인식: 무한 렌더링의 원인

  • React 애플리케이션에서 무한 렌더링은 주로 useEffect의 의존성 배열상태(State) 업데이트 로직이 잘못 맞물릴 때 발생합니다.

➕ 주요 원인 분석

  1. 불안정한 의존성 (Unstable Dependencies):

    • useEffect의 의존성 배열에 객체나 함수가 포함될 경우, 이들은 컴포넌트가 리렌더링될 때마다 새로운 참조(메모리 주소)를 갖게 됩니다.
    • useEffect는 의존성의 참조가 변경되었다고 인식하여, 실제 내용이 같더라도 콜백 함수를 불필요하게 재실행합니다. 이 콜백 함수가 다시 상태를 변경하면 무한 루프가 발생합니다.
  2. 상태 업데이트 로직의 부작용:

    • useEffect 내부에서 상태를 업데이트하는 로직이, 다시 그 useEffect를 촉발시키는 의존성 값을 변경하는 경우 무한 렌더링이 발생합니다.
  3. 정리되지 않은 이벤트 리스너/타이머:

    • 컴포넌트가 리렌더링될 때마다 새로운 이벤트 리스너나 타이머가 중복으로 등록되고, 이전 것들이 정리되지 않으면 메모리 누수와 함께 의도치 않은 여러 번의 함수 호출을 유발할 수 있습니다.

✅ 2. 해결 전략 1: API 호출 제어 및 중복 방지

  • 네트워크 요청은 비동기 작업이므로, 응답을 기다리는 동안 사용자의 다른 인터랙션으로 인해 동일한 요청이 중복으로 발생하는 것을 막아야 합니다.

➕ 주요 기법

  1. useRef를 이용한 플래그(Flag) 관리:
    • loadingRef: API 호출이 시작될 때 true로 설정하고, 완료되면 false로 설정하는 "잠금(Lock)" 역할을 하는 ref를 둡니다. API 호출 전에 이 플래그를 확인하여, 이미 호출이 진행 중이면 새로운 호출을 막습니다.
    • lastFetchedRef: 마지막으로 성공적으로 데이터를 가져온 조건(e.g., 연도, 월)을 ref에 저장합니다. 다음 요청 시, 이 ref의 값과 현재 요청 조건이 동일하면 API 호출을 건너뛰어 캐싱(Caching)과 유사한 효과를 냅니다.
    • useState가 아닌 useRef인가?: ref의 값 변경은 리렌더링을 유발하지 않으므로, 이러한 메타데이터(플래그, 캐시 정보)를 관리하는 데 이상적입니다.

✅ 3. 해결 전략 2: useEffectuseCallback 최적화

  • 불안정한 의존성 문제를 해결하여 useEffect가 꼭 필요할 때만 실행되도록 제어합니다.
  1. useCallback으로 함수 메모이제이션:

    • useEffect의 의존성 배열에 포함시켜야 하는 함수는 useCallback으로 감싸서, 해당 함수가 의존하는 값이 변경될 때만 함수가 재생성되도록 합니다. 이를 통해 useEffect에 안정적인 참조를 제공할 수 있습니다.
  2. 의존성 배열 최소화:

    • useEffect의 의존성 배열에는 반드시 필요한 최소한의 값만 포함해야 합니다. 불필요한 의존성을 제거하여 의도치 않은 재실행을 방지합니다.
  3. 이벤트 리스너 정리 (Cleanup Function):

    • useEffect에서 windowdocument에 이벤트 리스너를 등록했다면, 반드시 반환(return)하는 정리 함수 내에서 removeEventListener를 호출해야 합니다. 이는 컴포넌트가 언마운트되거나 useEffect가 재실행되기 전에 이전 리스너를 제거하여 메모리 누수와 중복 실행을 방지합니다.

✅ 4. 해결 전략 3: UI 반응성 향상을 위한 상태 업데이트 최적화

  • 데이터 CRUD(생성, 수정, 삭제) 작업 후, 전체 데이터를 서버에서 다시 불러오는 것은 비효율적이며 사용자 경험을 저하시킵니다.
  1. 낙관적 업데이트 (Optimistic Update) / 로컬 상태 즉시 업데이트:
    • 개념: 서버에 데이터 변경을 요청한 후, 성공 응답을 기다리지 않고 즉시 프론트엔드의 상태를 먼저 업데이트하는 기법입니다.
    • 구현: 사용자가 새 일정을 생성하면, API 요청을 보냄과 동시에 반환될 것으로 예상되는 데이터를 기반으로 로컬 상태 배열(state)에 새 항목을 직접 추가합니다.
    • 효과: 서버의 응답 지연과 관계없이 UI가 즉각적으로 반응하므로, 사용자는 매우 빠른 경험을 하게 됩니다. (단, 요청 실패 시 롤백 처리 필요)

✅ 5. 추가 최적화 방안

  1. React.memo: props가 변경되지 않은 자식 컴포넌트의 불필요한 리렌더링을 방지하는 가장 기본적인 최적화 도구입니다.
  2. useMemo: 복잡하고 비용이 큰 계산 결과를 메모이제이션하여, 의존성이 변경될 때만 재계산하도록 합니다. 필터링이나 정렬된 리스트를 파생 상태로 만들 때 유용합니다.
  3. 가상화 (Virtualization): 수백, 수천 개의 항목을 가진 긴 리스트를 렌더링할 때, 화면에 보이는 부분만 DOM에 렌더링하는 기술입니다. (react-window, react-virtualized) 초기 렌더링 속도를 획기적으로 개선합니다.

📌 요약

  • 무한 렌더링은 주로 불안정한 의존성(useEffect에 포함된 함수/객체)과 상태 업데이트 로직의 충돌로 인해 발생합니다.
  • useRef플래그로 활용하여 API 호출의 중복을 방지하고, useCallback으로 함수를 메모이제이션하여 useEffect에 안정적인 의존성을 제공하는 것이 핵심 해결책입니다.
  • CRUD 작업 후에는 서버에서 데이터를 다시 불러오는 대신, 로컬 상태를 즉시 업데이트하여 사용자에게 빠른 UI 피드백을 제공해야 합니다.
  • React.memo, useMemo, 가상화는 애플리케이션의 규모가 커졌을 때 고려할 수 있는 추가적인 성능 최적화 도구입니다.

0개의 댓글