logo

Blog

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

#React#virtualize#library#virtualizing large data
ryxxn profileryxxn
2025.02.11
thumbnail

5만 개 가량의 차량 데이터를 관리하고, 편집, 맵핑, 엑셀 업로드, 다운로드가 가능한 툴을 만들어달라는 요청을 받았다.

한 차량에 대한 데이터도 많아 테이블이 500줄이 넘어가도 버벅거렸다.
그런데 500줄은 커녕 50,000줄의 데이터를 관리해야 했다.


1. react-virtualized 적용

그래서 react-virtualized라는 라이브러리를 사용했다.

그런데 한 row에 tooltip, drawer 기능이 포함되어 있는데,
react-virtualized는 컴포넌트 기반의 라이브러리였기 때문에
이런 상황에서 이 라이브러리로 스타일까지 커스텀하기에는 문제가 있었다.
table 태그와 ul 태그에서도 동일한 사용법으로 사용하고 싶었다.

그래서 hook을 이용해서 가상화를 적용시키면 스타일 및 컴포넌트에 구애를 받지 않겠다는 생각이 들었다.


2. hook 기반의 가상화 직접 구현

가상화는 구현하기에 크게 부담이 되지 않는 방법이었기 때문에 직접 구현하기로 했다.

virtualizing-example

가상화는 위 그림과 같은 원리로, 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 훅을 만들면 이렇게 사용이 가능하다.

tsx
import { 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에서 아주 유사한 방식으로 만든 가상화 라이브러리를 발견했다;;

https://tanstack.com/virtual/latest

tanstack에서 만든 라이브러리와 내가 생각한 방식이 거의 똑같다는 것이
처음엔 충격이었다가 뭔가 뿌듯하기도 했다.

내가 만든 코드는 다음 레포지토리에서 확인할 수 있다.

https://github.com/ryxxn/react-virtual-hook

Related Articles