테이블 성능 최적화: 가상화 (Virtualization)

React성능최적화가상화TanStackFE

1. 문제점

테이블에 수백, 수천 개 행(row) 동시 렌더링 시 다수 DOM 요소 생성으로 성능 문제 발생.

  • 느린 초기 렌더링: 페이지 로딩 시 브라우저의 전체 행 그리기 시간 과다.
  • 스크롤 시 버벅임(Lag): 스크롤 시 대량 요소 위치 재계산으로 오류와 같은 스크롤 버벅임 현상 발생.
  • 높은 메모리 사용량: 다량 DOM 요소 상주로 애플리케이션 전체 반응성 저하.

2. 해결 방안: 행 가상화 (Row Virtualization)

성능 문제 대응을 위한 행 가상화(Row Virtualization) 기법 도입.

  • 화면 가시 영역(viewport) 해당 행만 DOM 렌더링.

3. 구현 상세

@tanstack/react-virtual 기반 useVirtualizer 훅 사용.

주요 설정

// app/pages/RuleDetail/Table/Table.tsx

const wrapperRef = useRef<HTMLDivElement | null>(null); // 스크롤 컨테이너 Ref

const rowVirtualizer = useVirtualizer({
  count: rows.length,              // 전체 행 개수
  getScrollElement: () => wrapperRef.current, // 스크롤 이벤트 감지 요소
  estimateSize: () => 38,          // 행 예상 높이(px)
});
  • count: 가상화 대상 전체 아이템 총 개수.
  • getScrollElement: 스크롤 기준 컨테이너 DOM 요소.
  • estimateSize: 행 높이 추정값 제공을 통한 전체 스크롤 영역 높이 계산 및 스크롤바 정확도 확보.

💡 행 높이 가변성 대응을 위한 translateY(${virtualItem.start}px) 기반 위치 지정. virtualItem.start: 스크롤 컨테이너 top 기준 가상 아이템의 오프셋(px).

렌더링 방식

전체 rows 배열 직접 렌더링 대신 rowVirtualizer.getVirtualItems() 결과(가상 아이템 배열) 렌더링.

// 1. 전체 스크롤 공간 확보용 컨테이너
<div style={{ height: `${rowVirtualizer.getTotalSize()}px`, position: 'relative' }}>
  {/* 2. 화면에 보일 아이템만 렌더링 */}
  {rowVirtualizer.getVirtualItems().map((virtualItem) => {
    const row = rows[virtualItem.index];
    return (
      <div
        key={row.id}
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          // 3. `transform` 기반 행 위치 지정
          transform: `translateY(${virtualItem.start}px)`,
        }}
      >
        {/* ... 행(Row) 컨텐츠 ... */}
      </div>
    );
  })}
</div>
  1. 스크롤 공간 확보: 컨테이너에 rowVirtualizer.getTotalSize() 기반 전체 높이 할당으로 전체 행 존재와 동일한 스크롤바 동작 확보.
  2. 필요한 행만 렌더링: getVirtualItems() 결과에 현재 스크롤 위치 기준 필요 행 정보(index, start 등)만 포함.
  3. 효율적인 위치 지정: top/left 대신 transform: translateY 사용으로 리플로우(reflow) 최소화 및 스크롤 성능 개선.

4. 기대 효과

  • 초기 로딩 속도 향상: 가시 영역 일부 행만 렌더링으로 초기 로드 시간 단축.
  • 부드러운 스크롤: 스크롤 시 제한된 DOM 요소 교체로 높은 프레임 유지.
  • 메모리 사용량 감소: DOM 렌더링 요소 수 감소로 메모리 부담 완화.
  • 무한 스크롤과의 시너지: 데이터 추가에도 DOM 요소 수 일정 유지로 대용량 처리 시 성능 저하 방지.