React에서 가상 스크롤링 구현하기 – 개념부터 구현까지 완벽 마스터

React에서 가상 스크롤링 구현하기 – 개념부터 구현까지 완벽 마스터

1. 알고리즘 소개 및 개념

React에서 가상 스크롤링 구현하기는 대용량 데이터 목록을 효율적으로 렌더링하기 위한 핵심 알고리즘입니다. 수천, 수만 개의 항목을 일반적인 방법으로 렌더링하면 DOM 노드가 과도하게 생성되어 성능이 급격히 저하됩니다. 가상 스크롤링(Virtual Scrolling)은 현재 화면에 보이는 영역(Viewport)의 항목만 실제로 렌더링하고, 나머지는 렌더링하지 않음으로써 DOM 노드 수를 최소화합니다. 사용자가 스크롤할 때 보이는 항목을 동적으로 계산하여 업데이트하는 방식으로, React 애플리케이션에서 대용량 리스트의 성능 문제를 해결하는 가장 효과적인 기법입니다.

2. 동작 원리 상세 설명

가상 스크롤링의 핵심 원리는 ‘윈도우잉(Windowing)’ 기법입니다. 전체 데이터 배열 중에서 현재 스크롤 위치를 기준으로 화면에 표시될 항목의 시작 인덱스와 끝 인덱스를 계산합니다. 컨테이너의 전체 높이는 (항목 개수 × 항목 높이)로 설정하여 스크롤바가 정상적으로 동작하도록 합니다. 실제 DOM에는 가시 영역의 항목만 렌더링하되, 스크롤 시 깜빡임을 방지하기 위해 상하로 몇 개의 버퍼 항목을 추가로 렌더링합니다. 스크롤 이벤트가 발생하면 현재 스크롤 위치(scrollTop)를 기반으로 startIndex를 계산하고, 컨테이너 높이와 항목 높이를 고려하여 endIndex를 결정합니다. 렌더링되는 항목들은 absolute positioning을 통해 정확한 위치에 배치되며, transform: translateY() 또는 top 속성을 사용하여 각 항목의 Y축 오프셋을 설정합니다. 이를 통해 실제로는 수십 개의 DOM 노드만 존재하지만, 사용자에게는 전체 목록이 있는 것처럼 보이는 착시 효과를 만들어냅니다.

3. 시간/공간 복잡도 분석

시간 복잡도: 가상 스크롤링의 렌더링 시간 복잡도는 O(k)입니다. 여기서 k는 화면에 보이는 항목의 수로, 전체 데이터 개수 n과 무관한 상수입니다. 일반 렌더링의 O(n)과 비교하면 획기적인 성능 향상입니다. 스크롤 위치 계산은 O(1)의 상수 시간에 수행됩니다. 초기 렌더링 시에도 전체 데이터를 순회하지 않고 보이는 영역만 처리하므로 O(k)입니다.

공간 복잡도: 메모리 사용량 측면에서 DOM 노드는 O(k)만 유지됩니다. 일반 렌더링이 O(n)개의 DOM 노드를 생성하는 것과 대조적입니다. 다만 원본 데이터 배열은 여전히 O(n)의 공간을 차지하므로, 전체 공간 복잡도는 O(n + k)입니다. 실무에서는 k가 20-50 정도의 작은 상수이므로 n이 10,000개 이상일 때 메모리 효율이 극대화됩니다.

4. 단계별 구현 과정

React에서 가상 스크롤링 구현하기의 단계별 과정을 상세히 살펴보겠습니다.

4.1 기본 구조 설정

먼저 필요한 상태와 ref를 정의합니다. 스크롤 위치를 추적할 상태와 컨테이너 참조가 필요합니다.

import React, { useState, useRef, useEffect } from 'react';

const VirtualScroll = ({ items, itemHeight = 50, containerHeight = 600 }) => {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef(null);
  
  // 화면에 보이는 항목 개수 계산
  const visibleCount = Math.ceil(containerHeight / itemHeight);
  
  // 버퍼 항목 (스크롤 시 깜빡임 방지)
  const bufferCount = 3;
  
  // 시작 인덱스 계산
  const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - bufferCount);
  
  // 끝 인덱스 계산
  const endIndex = Math.min(
    items.length,
    startIndex + visibleCount + bufferCount * 2
  );
  
  // 실제 렌더링할 항목들
  const visibleItems = items.slice(startIndex, endIndex);
  
  return (
    
setScrollTop(e.target.scrollTop)} > {/* 전체 높이를 유지하는 스페이서 */}
{visibleItems.map((item, index) => { const actualIndex = startIndex + index; return (
{item}
); })}
); };

4.2 성능 최적화 버전

스크롤 이벤트 최적화를 위한 throttling을 적용한 고급 구현입니다.

import React, { useState, useRef, useCallback, useEffect } from 'react';

const OptimizedVirtualScroll = ({ 
  items, 
  itemHeight = 50, 
  containerHeight = 600,
  overscan = 3 // 버퍼 크기
}) => {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef(null);
  const throttleTimeout = useRef(null);
  
  // Throttled 스크롤 핸들러
  const handleScroll = useCallback((e) => {
    if (throttleTimeout.current) return;
    
    throttleTimeout.current = setTimeout(() => {
      setScrollTop(e.target.scrollTop);
      throttleTimeout.current = null;
    }, 16); // 60fps 유지
  }, []);
  
  // 정리 작업
  useEffect(() => {
    return () => {
      if (throttleTimeout.current) {
        clearTimeout(throttleTimeout.current);
      }
    };
  }, []);
  
  const visibleCount = Math.ceil(containerHeight / itemHeight);
  const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
  const endIndex = Math.min(items.length, startIndex + visibleCount + overscan * 2);
  const visibleItems = items.slice(startIndex, endIndex);
  const totalHeight = items.length * itemHeight;
  const offsetY = startIndex * itemHeight;
  
  return (
    
{visibleItems.map((item, index) => (
{item}
))}
); };

4.3 동적 높이 지원 버전

항목마다 높이가 다른 경우를 처리하는 고급 구현입니다.

const DynamicVirtualScroll = ({ items, estimatedItemHeight = 50, containerHeight = 600 }) => {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef(null);
  const itemHeights = useRef(new Map());
  const itemRefs = useRef(new Map());
  
  // 각 항목의 실제 높이 측정
  useEffect(() => {
    itemRefs.current.forEach((element, index) => {
      if (element) {
        const height = element.getBoundingClientRect().height;
        itemHeights.current.set(index, height);
      }
    });
  });
  
  // 누적 높이 계산
  const getItemOffset = (index) => {
    let offset = 0;
    for (let i = 0; i < index; i++) {
      offset += itemHeights.current.get(i) || estimatedItemHeight;
    }
    return offset;
  };
  
  // 스크롤 위치에서 시작 인덱스 찾기
  const findStartIndex = () => {
    let offset = 0;
    for (let i = 0; i < items.length; i++) {
      const height = itemHeights.current.get(i) || estimatedItemHeight;
      if (offset + height > scrollTop) return i;
      offset += height;
    }
    return 0;
  };
  
  const startIndex = findStartIndex();
  let currentOffset = getItemOffset(startIndex);
  const visibleItems = [];
  
  for (let i = startIndex; i < items.length && currentOffset < scrollTop + containerHeight; i++) {
    visibleItems.push({ item: items[i], index: i, offset: currentOffset });
    currentOffset += itemHeights.current.get(i) || estimatedItemHeight;
  }
  
  const totalHeight = getItemOffset(items.length);
  
  return (
    
setScrollTop(e.target.scrollTop)} style={{ height: containerHeight, overflow: 'auto', position: 'relative' }} >
{visibleItems.map(({ item, index, offset }) => (
itemRefs.current.set(index, el)} style={{ position: 'absolute', top: offset, width: '100%', padding: '16px' }} > {item}
))}
); };

5. 최적화 방법

1. 스크롤 이벤트 최적화: requestAnimationFrame 또는 throttle을 사용하여 스크롤 이벤트 처리 빈도를 제한합니다. 매 스크롤마다 리렌더링하면 성능이 저하되므로 16ms(60fps) 간격으로 제한하는 것이 좋습니다.

2. 메모이제이션: React.memo를 활용하여 개별 항목 컴포넌트를 메모이제이션하면 불필요한 리렌더링을 방지할 수 있습니다. useMemo로 계산된 값들을 캐싱하는 것도 효과적입니다.

3. CSS 최적화: transform과 will-change 속성을 사용하여 GPU 가속을 활용합니다. position: absolute 대신 transform: translateY()를 사용하면 레이아웃 재계산 없이 렌더링할 수 있습니다.

4. 가상화 라이브러리 활용: react-window나 react-virtualized 같은 검증된 라이브러리를 사용하면 복잡한 엣지 케이스까지 처리된 안정적인 구현을 바로 사용할 수 있습니다.

import { FixedSizeList } from 'react-window';

const OptimizedList = ({ items }) => (
  
    {({ index, style }) => (
      
{items[index]}
)}
);

6. 실전 활용 예제

대용량 사용자 목록, 채팅 메시지 히스토리, 무한 스크롤 피드 등에서 가상 스크롤링이 필수적입니다. 예를 들어 10,000개의 상품 목록을 표시하는 전자상거래 사이트에서 일반 렌더링은 초기 로딩에 5-10초가 소요되지만, 가상 스크롤링을 적용하면 0.3초 이내로 단축됩니다. React에서 가상 스크롤링 구현하기를 통해 사용자 경험을 획기적으로 개선할 수 있습니다.

// 실전 예제: 대용량 데이터 테이블
const App = () => {
  const data = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
  
  return (
    

Virtual Scroll Demo

); };

React에서 가상 스크롤링 구현하기는 현대 웹 애플리케이션의 필수 기술입니다. 이 기법을 마스터하면 어떤 규모의 데이터도 효율적으로 처리할 수 있습니다.

📚 함께 읽으면 좋은 글

1

React에서 가상 스크롤링 구현하기 – 개념부터 구현까지 완벽 마스터

📂 알고리즘 해결
📅 2025. 10. 10.
🎯 React에서 가상 스크롤링 구현하기

2

React에서 가상 스크롤링 구현하기 – 개념부터 구현까지 완벽 마스터

📂 알고리즘 해결
📅 2025. 10. 8.
🎯 React에서 가상 스크롤링 구현하기

3

React에서 가상 스크롤링 구현하기 – 개념부터 구현까지 완벽 마스터

📂 알고리즘 해결
📅 2025. 10. 8.
🎯 React에서 가상 스크롤링 구현하기

4

React에서 가상 스크롤링 구현하기 – 개념부터 구현까지 완벽 마스터

📂 알고리즘 해결
📅 2025. 10. 2.
🎯 React에서 가상 스크롤링 구현하기

5

재귀 함수 마스터하기 – 개념부터 구현까지 완벽 마스터

📂 알고리즘 해결
📅 2025. 10. 22.
🎯 재귀 함수 마스터하기

💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!

📢 이 글이 도움되셨나요? 공유해주세요!

여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨

🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏

💬 여러분의 소중한 의견을 들려주세요!

이 글에서 가장 도움이 된 부분은 어떤 것인가요?

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨

🔔 블로그 구독하고 최신 글을 받아보세요!

📚
다양한 주제
17개 카테고리

정기 업데이트
하루 3회 발행

🎯
실용적 정보
바로 적용 가능

💡
최신 트렌드
2025년 기준

🌟 알고리즘 해결부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨

📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!

답글 남기기