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>
- 스크롤 공간 확보: 컨테이너에
rowVirtualizer.getTotalSize()기반 전체 높이 할당으로 전체 행 존재와 동일한 스크롤바 동작 확보. - 필요한 행만 렌더링:
getVirtualItems()결과에 현재 스크롤 위치 기준 필요 행 정보(index,start등)만 포함. - 효율적인 위치 지정:
top/left대신transform: translateY사용으로 리플로우(reflow) 최소화 및 스크롤 성능 개선.
4. 기대 효과
- 초기 로딩 속도 향상: 가시 영역 일부 행만 렌더링으로 초기 로드 시간 단축.
- 부드러운 스크롤: 스크롤 시 제한된 DOM 요소 교체로 높은 프레임 유지.
- 메모리 사용량 감소: DOM 렌더링 요소 수 감소로 메모리 부담 완화.
- 무한 스크롤과의 시너지: 데이터 추가에도 DOM 요소 수 일정 유지로 대용량 처리 시 성능 저하 방지.