[react-virtual] virtualization으로 데이터 렌더링 최적화하기
💡 시작하기
12월 업무에서 약 2,000개에 달하는 발전소 데이터를 테이블로 렌더링하는 과정에서 심각한 성능 이슈를 겪었다. 이에 react-virtual을 도입한 경험과 공부한 내용들에 대해 적어보려고 한다.
1️⃣ react-virtual
react-virtual ? 많은 요소의 리스트를 가상화하는 라이브러리
여기서 말하는 가상화를 쉽게 말해본다면, 보이는 만큼만 그린다 라는 의미이다.
예를 들어 1000개의 데이터 중 화면 상에 20개만 보이게 한다면, 20개만 DOM에 올리고, 스크롤 위치에 따라 데이터를 바꿔간다는 뜻이다
example code
const parentRef = useRef(null);
const rowVirtualizer = useVirtualizer({
count: rows.length,
estimateSize: () => 48,
getScrollElement: () => parentRef.current,
overscan: 3,
});
const virtualItems = rowVirtualizer.getVirtualItems();
..
{virtualItems.map((row) => (
<TableRow key={row.id} onDoubleClick={() => onRowDoubleClick?.(row)}>
{row.getVisibleCells().map((cell) => {
const cellContent = flexRender(
cell.column.columnDef.cell,
cell.getContext(),
);
return (
<TableCell
key={cell.id}
className={cn(
"h-12 overflow-hidden whitespace-nowrap border-0 p-3 first:pl-6 last:pr-6",
)}
style={{
width: `${cell.column.getSize()}px`,
}}
>
{cellContent}
</TableCell>
);
})}
</TableRow>
))}
- count : 전체 행의 수
- estimateSize: 행 하나의 높이
- 실제 행의 높이와 크게 달라지면 스크롤 위치가 튀는 현상이 발생함
- getScrollElement : 어떤 DOM의 scrollTop을 기준으로 계산할지 지정
- overscan : 화면에 보이는 행의 수보다 더 렌더링할 행의 수
- 스크롤할 때 지지직 거리는 오류가 발생함
- 미리 준비해두는 버퍼 역할!
- useVirtualizer : 현재 스크롤 위치를 기준으로 지금 렌더링되어야 할 행 목록을 계산
2️⃣ 내가 도입한 방식 : react-virtual + shadcn/ui datatable
기존에 서비스가 가지고 있었던 문제 상황
- 서버에서 몇천 개의 데이터를 한꺼번에 불러오는 상황
- 하나의 행에 들어가는 데이터의 종류가 굉장히 많음
⇒ 이 테이블이 있는 페이지를 새로고침하면 약 6-7초의 로딩 후 테이블이 보이게 됨
이 data-table의 형태를 유지하되, virtual scroll을 도입하기로 했다. virtual scroll을 도입하면 무한 스크롤의 성능과 비슷하게 만들 수 있기 때문이다!
기존에는 한 번에 모든 리스트의 행이 렌더링되었었지만 virtual scroll을 도입하면서 정해진 개수의 행만 렌더링되고 있다.
기존에는 행의 개수가 1,234개 라고 하면 DOM에 있는 행의 수는 1,234개였지만 virtual scroll을 도입한 지금, DOM에 있는 행의 수는 22개로 확연히 줄어들었다.
3️⃣ 성능 결과
왼쪽이 Before, 오른쪽이 After다
수치로 확인한 결과, 개선 폭은 예상보다 훨씬 컸다..
렌더링
- 350ms → 39ms
⇒ 약 89% 단축 시킴
페인팅
- 50ms → 24ms
⇒ 약 52% 단축 시킴
스크립트는 1ms에서 410ms 증가했지만, 가상화 로직이 추가된 자연스러운 결과이다. 기존에 브라우저 레이아웃이 담당하던 작업들이 JS 영역으로 이동한 것으로 해석될 수 있다.
결과적으로 DOM 노드 수가 대폭 감소하며 reflow/repaint 병목이 해소되었고, 대용량 테이블에서도 안정적인 UX를 제공할 수 있게 되었다.
📝 정리
이번 작업은 처음으로 ‘성능’이라는 관점에서 프론트엔드를 바라본 경험이었다. react-virtual을 적용하며 DOM 구조가 성능에 얼마나 큰 영향을 주는지 체감했고, 단순히 기능 구현을 넘어 렌더링 방식 자체를 설계하는 과정이 중요하다는 걸 배웠다.
특히 2,000개 이상의 데이터를 다루는 실무 환경에서 렌더링 시간을 대폭 단축하며, 대량의 데이터를 사용해도 쾌적한 UX를 제공할 수 있다는 자신감을 얻었다.!!
