Tips & Tricks

Claude Codeで仮想スクロールを実装する方法

Claude Codeを活用して大量データを高速描画する仮想スクロール(ウィンドウイング)を実装する方法を解説します。

仮想スクロールが必要な場面

数千〜数万件のリストを表示する際、すべてのDOM要素を描画するとブラウザのパフォーマンスが大幅に低下します。仮想スクロール(ウィンドウイング)は、画面に見えている部分だけをDOMに描画する手法です。Claude Codeを使えば、この高度な最適化技術を効率的に実装できます。

基本的な仮想スクロールの実装

> 固定高さアイテムの仮想スクロールコンポーネントを作って。
> 10万件のリストでもスムーズに動くようにして。
import { useState, useRef, useCallback, useMemo } from 'react';

interface VirtualListProps<T> {
  items: T[];
  itemHeight: number;
  containerHeight: number;
  overscan?: number;
  renderItem: (item: T, index: number) => React.ReactNode;
}

function VirtualList<T>({
  items, itemHeight, containerHeight, overscan = 5, renderItem,
}: VirtualListProps<T>) {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef<HTMLDivElement>(null);

  const totalHeight = items.length * itemHeight;
  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 = useMemo(
    () => items.slice(startIndex, endIndex),
    [items, startIndex, endIndex]
  );

  const handleScroll = useCallback(() => {
    if (containerRef.current) {
      setScrollTop(containerRef.current.scrollTop);
    }
  }, []);

  return (
    <div
      ref={containerRef}
      onScroll={handleScroll}
      style={{ height: containerHeight, overflow: 'auto' }}
      role="list"
      aria-label={`リスト(全${items.length}件)`}
    >
      <div style={{ height: totalHeight, position: 'relative' }}>
        {visibleItems.map((item, i) => {
          const index = startIndex + i;
          return (
            <div
              key={index}
              role="listitem"
              style={{
                position: 'absolute',
                top: index * itemHeight,
                height: itemHeight,
                width: '100%',
              }}
            >
              {renderItem(item, index)}
            </div>
          );
        })}
      </div>
    </div>
  );
}

可変高さアイテムへの対応

> アイテムごとに高さが異なる仮想スクロールにも対応して。
class DynamicSizeManager {
  private heights: Map<number, number> = new Map();
  private estimatedHeight: number;

  constructor(estimatedHeight: number) {
    this.estimatedHeight = estimatedHeight;
  }

  setHeight(index: number, height: number) {
    this.heights.set(index, height);
  }

  getHeight(index: number): number {
    return this.heights.get(index) ?? this.estimatedHeight;
  }

  getOffset(index: number): number {
    let offset = 0;
    for (let i = 0; i < index; i++) {
      offset += this.getHeight(i);
    }
    return offset;
  }

  getTotalHeight(itemCount: number): number {
    return this.getOffset(itemCount);
  }

  findIndexAtOffset(offset: number, itemCount: number): number {
    let current = 0;
    for (let i = 0; i < itemCount; i++) {
      current += this.getHeight(i);
      if (current > offset) return i;
    }
    return itemCount - 1;
  }
}

高さ自動計測コンポーネント

import { useRef, useEffect } from 'react';

function MeasuredItem({ index, onMeasure, children }: {
  index: number;
  onMeasure: (index: number, height: number) => void;
  children: React.ReactNode;
}) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (ref.current) {
      const observer = new ResizeObserver(([entry]) => {
        onMeasure(index, entry.contentRect.height);
      });
      observer.observe(ref.current);
      return () => observer.disconnect();
    }
  }, [index, onMeasure]);

  return <div ref={ref}>{children}</div>;
}

TanStack Virtualの活用

より本格的な実装にはTanStack Virtualが便利です。

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualizedList({ items }: { items: string[] }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
    overscan: 10,
  });

  return (
    <div ref={parentRef} style={{ height: 600, overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              transform: `translateY(${virtualItem.start}px)`,
              width: '100%',
            }}
          >
            {items[virtualItem.index]}
          </div>
        ))}
      </div>
    </div>
  );
}

まとめ

Claude Codeを使えば、固定高さ・可変高さの仮想スクロールから、TanStack Virtualの活用まで効率的に実装できます。無限スクロールとの組み合わせは無限スクロール実装を、全般的なパフォーマンス改善はパフォーマンス最適化を参照してください。

TanStack Virtualの詳細はTanStack Virtual公式ドキュメントをご覧ください。

#Claude Code #仮想スクロール #パフォーマンス #React #ウィンドウイング