React Virtual
@tanstack/react-virtual 어댑터는 핵심 가상화 로직을 감싸는 래퍼입니다.
useVirtualizer
function useVirtualizer<TScrollElement, TItemElement = unknown>(
options: PartialKeys<
ReactVirtualizerOptions<TScrollElement, TItemElement>,
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
>,
): Virtualizer<TScrollElement, TItemElement>
이 함수는 HTML 요소를 scrollElement로 사용하도록 구성된 표준 Virtualizer 인스턴스를 반환합니다.
useWindowVirtualizer
function useWindowVirtualizer<TItemElement = unknown>(
options: PartialKeys<
ReactVirtualizerOptions<Window, TItemElement>,
| 'getScrollElement'
| 'observeElementRect'
| 'observeElementOffset'
| 'scrollToFn'
>,
): Virtualizer<Window, TItemElement>
이 함수는 window를 scrollElement로 사용하도록 구성된 window 기반 Virtualizer 인스턴스를 반환합니다.
React 전용 옵션
useFlushSync
type ReactVirtualizerOptions<TScrollElement, TItemElement> =
VirtualizerOptions<TScrollElement, TItemElement> & {
useFlushSync?: boolean
}
useVirtualizer와 useWindowVirtualizer는 모두 useFlushSync 옵션을 받으며, 이 옵션은 동기 업데이트에 React의 flushSync를 사용할지 제어합니다.
- 타입:
boolean - 기본값:
true - 설명:
true이면 버추얼라이저가flushSync를react-dom에서 가져와 사용하여 스크롤 이벤트 중 동기 렌더링을 보장합니다. 가장 정확한 스크롤 동작을 제공하지만 일부 상황에서는 성능에 영향을 줄 수 있습니다.
useFlushSync를 비활성화해야 하는 경우
다음과 같은 경우에는 useFlushSync: false로 설정하는 것을 고려할 수 있습니다.
- React 19 호환성: React 19에서는 스크롤할 때 다음과 같은 콘솔 경고가 표시될 수 있습니다.
flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task.useFlushSync: false로 설정하면 React가 업데이트를 자연스럽게 배칭할 수 있어 이 경고가 사라집니다. - 성능 최적화: 저사양 기기에서 빠르게 스크롤할 때 성능 문제가 발생하는 경우
- 테스트 환경: 동기 DOM 업데이트가 필요하지 않은 테스트를 실행하는 경우
- 중요하지 않은 목록: 전반적인 성능을 개선하기 위해 스크롤 중 약간의 시각적 지연을 허용할 수 있는 경우
예시
const virtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
useFlushSync: false, // Disable synchronous updates
})
directDomUpdates
- 타입:
boolean - 기본값:
false - 설명: 스크롤 전용 업데이트에서는 React의 재렌더링을 건너뜁니다. 활성화하면 버추얼라이저가 항목 위치(
top/left또는transform)와 컨테이너 크기(height/width)를 DOM에 직접 쓰고, 표시되는 인덱스 범위나isScrolling이 변경될 때만 재렌더링합니다.
활성화 시 요구 사항
- 항목 요소에는
position: absolute를 지정해야 하며,'transform'모드에서는top: 0/left: 0으로 기준점도 고정해야 합니다. - 항목 요소는 스타일에서 주축 위치를 설정하면 안 됩니다. 버추얼라이저가
top/left는'position'모드에서,transform은'transform'모드에서 관리합니다. - 내부 크기 컨테이너는
virtualizer.containerRef를 전달받아야 하며 스타일에서height/width를 설정하면 안 됩니다. - 다중 레인 레이아웃(그리드 / 메이슨리)에서는 교차축 위치(예:
left: ${(item.lane * 100) / lanes}%)가 항목별로 고정되므로 JSX에서 계속 설정해야 합니다. 자동화되는 것은 주축뿐입니다.
⚠️ 이 플래그는 마운트할 때 한 번만 설정하도록 설계되었습니다. 런타임에 이 플래그(또는
directDomUpdatesMode)를 전환하면 항목과 컨테이너에 더 이상 유효하지 않은 인라인 스타일이 남을 수 있습니다.
참고:
containerRef를 생략하면 버추얼라이저가 DOM에 직접 아무것도 쓰지 않습니다. 즉, 항목 위치와 컨테이너 크기를 모두 쓰지 않습니다. 이 경우 재렌더링을 건너뛰는 이점은 그대로 얻으면서 항목 배치와 컨테이너 크기 설정을 직접(예:onChange에서) 처리해야 합니다.
예시
const virtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
directDomUpdates: true,
})
return (
<div ref={parentRef} style={{ overflow: 'auto', height: 400 }}>
{/* The inner container must use virtualizer.containerRef and not set height */}
<div ref={virtualizer.containerRef} style={{ position: 'relative' }}>
{virtualizer.getVirtualItems().map((item) => (
<div
key={item.key}
ref={virtualizer.measureElement}
data-index={item.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
// Do NOT set top/left/transform — the virtualizer handles it
}}
>
Row {item.index}
</div>
))}
</div>
</div>
)
directDomUpdatesMode
- 타입:
'position' | 'transform' - 기본값:
'transform' - 설명:
directDomUpdates가 항목 요소의 위치를 지정하는 방식을 제어합니다.'transform'(기본값):transform: translate3d(...)를 씁니다. 항목을 각각의 컴포지터 레이어로 승격하므로 일반적으로 긴 목록에서 더 부드럽지만, 쌓임 맥락을 생성하고position: fixed인 하위 요소의 작동을 방해할 수 있습니다. 항목 요소는position: absolute,top: 0,left: 0으로 기준점을 고정해야 합니다.'position':top/left를 씁니다. 항목 요소는position: absolute여야 합니다.
예시
const virtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
directDomUpdates: true,
directDomUpdatesMode: 'position', // Use top/left instead of transform
})