대용량 엑셀 데이터 가상화 적용하기


5만 개 가량의 차량 데이터를 관리하고, 편집, 맵핑, 엑셀 업로드, 다운로드가 가능한 툴을 만들어달라는 요청을 받았다.
한 차량에 대한 데이터도 많아 테이블이 500줄이 넘어가도 버벅거렸다.
그런데 500줄은 커녕 50,000줄의 데이터를 관리해야 했다.
1. react-virtualized 적용
그래서 react-virtualized라는 라이브러리를 사용했다.
그런데 한 row에 tooltip
, drawer
기능이 포함되어 있는데,
react-virtualized
는 컴포넌트 기반의 라이브러리였기 때문에
이런 상황에서 이 라이브러리로 스타일까지 커스텀하기에는 문제가 있었다.
table
태그와 ul
태그에서도 동일한 사용법으로 사용하고 싶었다.
그래서 hook
을 이용해서 가상화를 적용시키면 스타일 및 컴포넌트에 구애를 받지 않겠다는 생각이 들었다.
2. hook 기반의 가상화 직접 구현
가상화는 구현하기에 크게 부담이 되지 않는 방법이었기 때문에 직접 구현하기로 했다.

가상화는 위 그림과 같은 원리로, container 또는 viewport에 들어온 리스트만 렌더링하는 방식이다.
구현을 하면 다음과 같다.
typescript// ... const scrollYHandler = (newScrollTop: number) => { /** * 스크롤을 한 번에 많이 움직였을 때 렌더링이 맞지 않는 문제가 있어 * react-dom의 flushSync를 이용해 강제 렌더링 **/ flushSync(() => { setIsScrollingY(true); setScrollTop(newScrollTop); }); if (scrollTimeoutYRef.current) clearTimeout(scrollTimeoutYRef.current); scrollTimeoutYRef.current = setTimeout( () => setIsScrollingY(false), SCROLLING_WAIT );}; const handleScroll = React.useCallback(() => { requestAnimationFrame(() => { const newScrollTop = containerRef.current?.scrollTop ?? 0; const newScrollLeft = containerRef.current?.scrollLeft ?? 0; const scrollingX = newScrollLeft !== scrollLeft; const scrollingY = newScrollTop !== scrollTop; if (scrollingX) scrollXHandler(newScrollLeft); if (scrollingY) scrollYHandler(newScrollTop); });}, []); React.useEffect(() => { const container = containerRef.current; if (!container) return; container.addEventListener('scroll', handleScroll, { passive: true }); // initial scroll position setScrollTop(0); setScrollLeft(0); return () => { container.removeEventListener('scroll', handleScroll); if (scrollTimeoutXRef.current) clearTimeout(scrollTimeoutXRef.current); if (scrollTimeoutYRef.current) clearTimeout(scrollTimeoutYRef.current); };}, []); // ...
이렇게 구현할 수 있다.
3. 적용하기
위의 원리로 useVirtualList
훅을 만들면 이렇게 사용이 가능하다.
tsximport { useVirtualList } from 'use-virtual-list'; const MyList = () => { const virtualizeManager = useVirtualList({ data: DATA, // 가상화를 적용할 데이터 height: 500, rowHeight: 50, }); return ( <div ref={containerRef} style={virtualizedManager.getContainerStyle()}> <ul style={virtualizedManager.getListStyle()}> {visibleData.map((item, index) => ( <li key={item.id} style={virtualizedManager.getRowStyle(index)} className="border p-2" > {item.name} </li> ))} </ul> </div> );};
4. 마무리
만들고 나서 사용하다가 npm에 올리려고 찾아보다 보니
tanstack
에서 아주 유사한 방식으로 만든 가상화 라이브러리를 발견했다;;
tanstack
에서 만든 라이브러리와 내가 생각한 방식이 거의 똑같다는 것이
처음엔 충격이었다가 뭔가 뿌듯하기도 했다.
내가 만든 코드는 다음 레포지토리에서 확인할 수 있다.
https://github.com/ryxxn/react-virtual-hookRelated Articles
- 모바일 키보드를 고려한 버튼 위치 맞추기입력창에 맞춰 키보드가 올라오면 버튼이 가려지는 경우 버튼을 키보드에 맞춰 올린 경험이에요2024.08.08ryxxn
- React 모달 라이브러리를 만들기까지의 변천사저는 간단한 모달 컴포넌트조차 매번 넣어야 하는 게 불편했어요. 그래서 모달 컴포넌트를 함수로 간단히 실행하는 발상에서 시작해, react-handle-alert라는 라이브러리를 탄생시키기까지의 여정을 담았습니다.2024.05.04ryxxn
- React Quill에서 이미지를 업로드하기 위한 올바른 방법검색 결과에 나온 코드를 참고했다가 버그가 생겨 한참을 헤맸던 경험이에요. React에서 DOM API를 사용했을 때 발생한 버그를 겨우 찾은 이야기2024.04.30ryxxn
- Firebase와 Algolia 동기화 지연 문제 해결하기firebase database와 algolia 간의 동기화 사이에 data fetching했을 때 싱크가 맞지 않는 문제를 찾고 해결한 경험이에요2024.03.24ryxxn