가상화(Virtualization) 를 통해 성능을 최적화

Jinmin Kim·2025년 3월 13일

📌어떤 라이브러리를 선택해야 할까?

라이브러리장점단점추천 사용처
react-virtualized테이블, 그리드 지원, CellMeasurer 활용 가능무겁고 복잡함테이블, 가변 높이 리스트
react-window성능 빠름, 가벼움, 간단한 API가변 높이 지원 X고정 크기 리스트, 무한 스크롤
TanStack Virtual최신 기술 기반, 프레임 드랍 없음, 초경량직접 구현 필요커스텀 가상화, 초경량 가상화

📌react-virtualized

📌react-virtualized 동작 원리

1. 가상화(Virtualization)

  • 목적: DOM에 모든 행을 렌더링하지 않고, 화면에 보이는 행만 렌더링하여 성능을 최적화합니다.
  • 방법:
    • ListrowHeightrowCount를 기준으로 렌더링할 행의 위치를 계산합니다.
    • 스크롤 위치에 따라 보이는 행만 DOM에 추가하고, 나머지는 제거합니다.
    • 계산에 필요한 rowHeight, scrollTop, height 값을 활용하여 렌더링할 행의 범위를 결정합니다.

2. AutoSizer

  • 목적: 부모 컨테이너의 크기를 동적으로 감지합니다.
  • 동작:
    • 부모 컨테이너의 widthheight를 감지하고, 이를 List에 전달합니다.
    • 부모 요소의 CSS 설정이 중요하며, 높이나 너비가 명확하지 않으면 올바른 값을 감지하지 못합니다.

3. rowHeight의 역할

  • ListrowHeight 값을 기반으로 각 행의 위치를 계산합니다.
  • rowHeight가 고정값이면 모든 행의 높이가 같다고 가정합니다.
  • 동적인 높이의 경우, CellMeasurer를 사용해 각 행의 높이를 측정하고 캐싱합니다.

🔥CellMeasurerCache:

  • 행 높이를 동적으로 캐싱하여 성능을 유지합니다.
  • defaultHeight로 기본 높이를 설정하되, 각 행의 높이를 실시간으로 측정합니다.

🔥rowHeight 함수:

  • cache.rowHeight를 사용해 측정된 행 높이를 반환합니다.

🔥CellMeasurer:

  • rowRenderer 안에서 각 행을 감싸, 높이를 동적으로 측정합니다.

    // CellMeasurer를 위한 캐시 생성
    const cache = new CellMeasurerCache({
        fixedWidth: true, // 너비는 고정
        defaultHeight: 100, // 초기 기본값
        minHeight: 30, // 최소값
    });

     <AutoSizer>
        {({width, height}) => (
            <List
                className="custom-list"
                ref={listRef}
                width={width}
                height={height}
                rowCount={comparsionData.length}
                deferredMeasurementCache={cache}
                rowHeight={cache.rowHeight}
                rowRenderer={rowRenderer}
            />
        )}
    </AutoSizer>

📌react-window

📌 1. <List> 컴포넌트 옵션 정리

react-window<List>세로 스크롤되는 1차원 리스트(Vertical List) 를 가상화하여 성능을 최적화합니다.

기본 옵션

옵션명타입설명기본값
heightnumber리스트 전체의 높이 (스크롤 가능한 영역)필수
widthnumber or "100%"리스트 전체의 너비필수
itemCountnumber리스트 아이템 개수필수
itemSizenumber각 행(row)의 높이 (픽셀 단위, 고정 크기)필수
overscanCountnumber미리 렌더링할 추가 아이템 개수 (스크롤 성능 개선)1
classNamestringList에 추가할 CSS 클래스명없음
styleCSSPropertiesList의 스타일 커스텀없음

Grid는 2차원 가상화 (행 + 열)

Grid"행(row)과 열(column) 둘 다 가상화" 할 수 있는 2차원 리스트입니다.

✔️ 스크롤 방향: 위 ↕️ 아래 + 좌 ↔️ 우

✔️ 가상화 적용 대상: 행(row) & 열(column)

✔️ 테이블, 데이터 그리드, Excel 같은 구조에서 필수!

✔️ 2차원 데이터를 표현할 때 적합 (예: 테이블, 대시보드, 캘린더)

📌 예제 (Grid)

<Grid
  columnCount={10} // 10개의 열
  rowCount={1000} // 1000개의 행 (모두 렌더링 X)
  columnWidth={100} // 각 열의 너비
  rowHeight={50} // 각 행의 높이
  height={500} // 전체 그리드 높이
  width={800} // 전체 그리드 너비
>
  {({ columnIndex, rowIndex, style }) => (
    <div style={{ ...style, border: "1px solid black" }}>
      Cell {rowIndex}, {columnIndex}
    </div>
  )}
</Grid>

📝 결과:

  • Grid세로(height)와 가로(width) 스크롤이 모두 존재
  • 표처럼 행(row)과 열(column)이 있는 구조
  • 보이는 셀(Cell)만 가상화하여 렌더링!

🔥진행중 이슈

  1. 그리드가 전체적으로 꽉 차지않고, width가 아주 일부분으로만 잡혀있는 현상

List or Grid의 부모 Container에 아래의 css를 입력

position: absolute
  1. table 구조를 가지고 <List 컴포넌트를 구현 해보려함
    header의 width를 가지고, 동적으로 body의 각각의 Grid에 width를 적용 하려했으나.

    위와같이 스크롤이 생기면서 th, td의 width가 맞지 않는 현상이 생김.

브라우저들의 스크롤 차이으로 인해 width가 다르므로
각 스크롤 부분 만큼의 width를 계산하여. 그만큼 동적으로 padding-right를 추가.
1.offsetWidth
2.clientWidth
3.scrollWidth


useEffect(() => {
  setScrollbarWidth(getScrollbarWidth());

  //`table` 크기 변경 감지 (리사이즈 대응)
  const handleResize = () => {
    setScrollbarWidth(getScrollbarWidth());
  };

  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);

const getScrollbarWidth = () => {
// 스크롤바가 `clientWidth`에서 제외된 경우
   if (tableRef.current.offsetWidth !== tableRef.current.clientWidth) {
      return tableRef.current.offsetWidth - tableRef.current.clientWidth;
   }

// MacOS처럼 `scrollbar`가 숨겨진 경우, `scrollWidth`로 비교
  return tableRef.current.scrollWidth > tableRef.current.clientWidth
      ? tableRef.current.scrollWidth - tableRef.current.clientWidth
      : 0;
}
  1. css border-collapse: collapse; 이슈

해당 css는 padding을 없애버리는 이슈가 있다.
그걸모르고 계속 위의 css가 걸려있는데도, 계속 padding을 주려고하였다.
css를 수정할수는 없어서. 아래와 같이 적용시켰다.

<BorderCollapseComponent>
	<div style={{padding-right: ${scrollbarWidth}}}>
	</div>
</BorderCollapseComponent>

📌TanStack Virtual

profile
Let's do it developer

0개의 댓글