mcpField
L
lazy-loading

lazy-loading

github.com/yanko-belov開発1372 回インストール40 週間前に更新
リポジトリ

lazy-loading について

lazy-loading は、必要になるまでデータやページコンテンツの読み込みを遅らせる開発スキルです。操作、スクロール、ルート、ページネーションに応じた読み込みパターンを示します。

主な機能

  1. 01操作時のデータ読み込み
  2. 02表示領域に入ったセクションの読み込み
  3. 03ルート単位でのコード読み込み延期
  4. 04ページネーションや無限スクロール
  5. 05先読み過多の兆候の特定

活用シーン

  1. 01多くのデータを扱うダッシュボードの高速化
  2. 02非表示タブへの不要なリクエストの回避
  3. 03大きなリストの段階的な表示

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/yanko-belov/code-craft --skill lazy-loading

インストール後、Claude Code で/lazy-loadingを入力して読み込まれたか確認します。

詳細

遅延読み込み

概要

必要になったときにデータを読み込み、前もって読み込まないでください。使わないかもしれないデータを取得してはいけません。

最初にデータを読み込むと、帯域幅、メモリ、時間が無駄になります。必要に応じて読み込むことで、パフォーマンスとユーザーエクスペリエンスが向上します。

使用する場面

  • ダッシュボードがすべてのデータを一度に読み込む
  • ページが非表示のタブのデータを取得する
  • 「念のため」のデータを読み込む
  • 初回読み込みが遅い

絶対ルール

NEVER load data until it's actually needed.

例外はありません:

  • 「必要になるかもしれない」から
  • 「並列処理のほうが速い」から
  • 「すべてを読み込むほうが簡単」だから
  • 「それほどデータ量が多くない」から

検出:先行読み込み過多の兆候

最初にすべてを読み込んでいる場合は、いったん止めてください:

// ❌ VIOLATION: Load everything upfront
async function loadDashboard(userId: string) {
  const [
    profile,
    preferences,
    notifications,    // User might not check
    recentActivity,   // Collapsed by default
    analytics,        // Expensive, rarely viewed
    recommendations,  // Below the fold
    fullHistory       // Paginated anyway
  ] = await Promise.all([
    fetchProfile(userId),
    fetchPreferences(userId),
    fetchNotifications(userId),
    fetchRecentActivity(userId),
    fetchAnalytics(userId),       // Takes 2 seconds!
    fetchRecommendations(userId),
    fetchFullHistory(userId)      // 10MB of data!
  ]);
  
  return { profile, preferences, notifications, ... };
}

問題点:

  • 最も遅いデータ取得がすべてをブロックする
  • 使われないデータにリソースを浪費する
  • 体感パフォーマンスが悪い

正しいパターン:必要に応じて読み込む

// ✅ CORRECT: Load critical data first, rest on demand

// Initial load - only what's immediately visible
async function loadDashboard(userId: string) {
  const [profile, preferences] = await Promise.all([
    fetchProfile(userId),
    fetchPreferences(userId)
  ]);
  
  return { profile, preferences };
}

// React component with lazy loading
function Dashboard({ userId }) {
  // Critical data loaded immediately
  const { profile, preferences } = useInitialData(userId);
  
  // Notifications: load when header mounts
  const notifications = useLazyQuery(
    () => fetchNotifications(userId),
    { loadOn: 'mount' }
  );
  
  // Analytics: load when tab is selected
  const [analyticsTab, setAnalyticsTab] = useState(false);
  const analytics = useLazyQuery(
    () => fetchAnalytics(userId),
    { loadOn: analyticsTab }
  );
  
  // History: load when scrolled into view
  const historyRef = useRef();
  const history = useLazyQuery(
    () => fetchHistory(userId),
    { loadOn: useIntersectionObserver(historyRef) }
  );
  
  return (
    <div>
      <Header profile={profile} notifications={notifications} />
      <Tabs>
        <Tab label="Overview">...</Tab>
        <Tab label="Analytics" onSelect={() => setAnalyticsTab(true)}>
          {analytics.loading ? <Skeleton /> : <AnalyticsChart data={analytics.data} />}
        </Tab>
      </Tabs>
      <div ref={historyRef}>
        {history.data && <HistoryList items={history.data} />}
      </div>
    </div>
  );
}

遅延読み込みの手法

1. 操作時に読み込む
// Load when user clicks tab
const [showDetails, setShowDetails] = useState(false);
const details = useQuery(fetchDetails, { enabled: showDetails });

<Tab onClick={() => setShowDetails(true)}>
  {details.data ?? <Skeleton />}
</Tab>
2. スクロール時に読み込む(Intersection Observer)
function LazySection({ loadFn, children }) {
  const ref = useRef();
  const [loaded, setLoaded] = useState(false);
  const data = useQuery(loadFn, { enabled: loaded });
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => entry.isIntersecting && setLoaded(true)
    );
    observer.observe(ref.current);
    return () => observer.disconnect();
  }, []);
  
  return <div ref={ref}>{data.data ? children(data.data) : <Skeleton />}</div>;
}
3. ルートごとに読み込む
// Next.js / React Router
const AnalyticsPage = lazy(() => import('./AnalyticsPage'));

<Route path="/analytics" element={
  <Suspense fallback={<Loading />}>
    <AnalyticsPage />
  </Suspense>
} />
4. ページネーション/無限スクロール
function OrderList() {
  const [page, setPage] = useState(1);
  const { data, hasMore } = useOrders({ page, limit: 20 });
  
  return (
    <>
      {data.map(order => <OrderRow key={order.id} order={order} />)}
      {hasMore && <button onClick={() => setPage(p => p + 1)}>Load More</button>}
    </>
  );
}

圧力への対処プロトコル

1. 「必要になるかもしれない」

圧力:「ユーザーが必要とする場合に備えて、今読み込んでおこう」

回答:「かもしれない」ということは、おそらく必要ありません。実際に必要になったときに読み込みます。

**対応:**憶測ではなく、必要に応じて読み込みます。

2. 「並列処理のほうが速い」

圧力:「すべてを並列に読み込むほうが、順番に読み込むより速い」

**回答:**不要なデータを並列に読み込むより、読み込まないほうが速いです。

**対応:**必要なものは並列化します。必要になるかもしれないものは遅延読み込みします。

3. 「すべてを読み込むほうが簡単」

圧力:「取得関数を1つにまとめるほうが簡単」

**回答:**遅くて無駄の多いシンプルなコードは、シンプルとは言えません。

**対応:**構造化された遅延読み込みは、保守しやすく高パフォーマンスです。

危険信号 ― いったん止めて再検討してください

  • ページ読み込み時に、5件以上の取得を含む Promise.all
  • 折りたたまれたセクションや非表示のセクションのデータを取得している
  • ページネーションせず、リスト全体を読み込んでいる
  • 初回のページ読み込みが遅い
  • 「読み込み中...」が1~2秒以上続く

これらはすべて、遅延読み込みを実装すべきということです。

クイックリファレンス

先行読み込み(通常は不適切)遅延読み込み(通常は適切)
マウント時にすべて読み込むまず表示されているコンテンツを読み込む
非表示のタブのデータを取得するタブが選択されたときに取得する
リスト全体を一度に読み込むページネーション/無限スクロール
画面下部のコンテンツIntersection Observer

よくある正当化(すべて誤り)

言い訳実際のところ
「必要になるかもしれない」必要になったときに読み込みます。
「並列処理のほうが速い」最も速いのは、読み込まないことです。
「簡単だから」遅いものはシンプルではありません。
「データ量は多くない」積み重なれば大きくなります。帯域幅にはコストがかかります。
「準備しておくほうがUXがよい」読み込みが遅いほうがUXは悪くなります。

要点

表示されているものを読み込み、残りは後回しにします。大きなリストはページネーションします。

初回読み込みでは、重要なデータだけを読み込みます。それ以外は、操作、スクロール、または画面遷移に応じて読み込みます。ユーザーが目にしないデータのために、待たせてはいけません。

コメント