언어·프레임워크/React.js

[React.js] React에서 대용량 데이터 효율적으로 렌더링하기: useVirtualizer

DandyNow 2025. 2. 4. 14:37
728x90
반응형

1. 대규모 데이터 렌더링의 도전 과제

현재 진행 중인 프로젝트에서 약 5000개의 객체를 가지고 있는 배열을 다루게 되었다. 이 배열 데이터를 테이블로 렌더링하였는데 역시나 성능 이슈가 발생했다. 이번 케이스와 같이 웹 애플리케이션에서 수천 또는 수만 개의 데이터 항목을 한 번에 렌더링하면-스크롤-성능이 크게 저하된다. 모든 항목을 DOM에 동시에 렌더링하면 브라우저 메모리 사용량이 급증하기 때문이다.

2. 가상화(Virtualization)란?

이 이슈를 해결하기 위해 가상화 기술을 적용하였다. 가상화는 현재 뷰포트에 보이는 항목만 실제로 렌더링하고, 스크롤에 따라 동적으로 항목을 로드하는 기술이다. 이를 통해 대용량 데이터를 매우 효율적으로 처리할 수 있다.

3. @tanstack/react-virtual의 useVirtualizer 소개

가상화 기술을 적용하기 위해 @tanstack/react-virtual 라이브러리의 useVirtualizer 훅을 사용하였다. React에서 가장 강력하고 유연한 가상화 솔루션 중 하나이다.

주요 특징

  • 고성능 가상 렌더링
  • 스크롤 위치 기반 동적 항목 렌더링
  • 오버스캔(Overscan) 지원
  • 다양한 리스트/그리드 레이아웃 지원

4. 실제 구현 예시

const rowVirtualizer = useVirtualizer({
  count: visibleData.length,      // 전체 데이터 길이
  getScrollElement: () => parentRef.current, // 스크롤 컨테이너
  estimateSize: () => 30,          // 각 행의 예상 높이
  overscan: 10                     // 추가로 미리 렌더링할 항목 수
});

핵심 구현 전략

  1. 동적 높이 관리
    • estimateSize로 각 행의 예상 높이 설정
    • 정확한 스크롤 계산을 위해 중요
  2. 위치 조정
    • CSS Transform을 사용해 각 행의 정확한 위치 지정
    • 하드웨어 가속을 활용한 부드러운 스크롤 성능
  3. transform: `translateY(${virtualRow.start}px)`
  4. 오버스캔(Overscan) 활용
    • 현재 뷰포트 주변의 추가 항목 미리 렌더링
    • 스크롤 경험 개선

5. 성능 최적화 팁

  • 고정된 행 높이 사용
  • table-layout: fixed 적용
  • 텍스트 오버플로우 처리
  • 최소한의 DOM 요소 사용

6. 주의할 점

  1. 너무 많은 오버스캔은 성능 저하 원인
    • 나의 경우 처음부터 overscan: 10을 적용하였고 변경의 필요성을 느끼지 못하였다.
  2. 정확한 행 높이 예측 중요
    • estimateSize와 행을 이루는 요소의 스타일(height)의 값을 잘 맞춰야 한다.
    • 가상화가 적용된 테이블을 스크롤링할 때 각 행(tr)의 높이가 일정하게 유지되지 않고 점점 커지는 현상이 발생하였기에 tr 태그에 다음과 같은 스타일을 추가하였다.
<tr
  key={item.mac}
  style={{
    position: 'absolute',
    top: 0,
    left: 0,
    width: '100%',
    height: '30px',
    transform: `translateY(${virtualRow.start}px)`,
    display: 'table',
    tableLayout: 'fixed',
  }}
>
  1. 인라인 스타일 사용
    • CSS class로 스타일을 적용하는 대신 인라인 스타일을 사용한다. 그 이유는 가상화된 테이블의 특성상 일반적인 CSS 규칙이 제대로 적용되지 않을 수 있기 때문이다.
  2. 페이지네이션 병행 고려
    • 대용량 데이터의 경우 페이지네이션과 병행을 고려한다.
    • 나의 경우 무한 스크롤을 함께 적용하였다.
728x90
반응형