React lazyとは?コンポーネントの遅延読み込みで高速化

React.lazyを使ったコンポーネントの遅延読み込みの基本から実践的な使い方まで解説。アプリケーションの高速化とパフォーマンス改善を実現

Learning Next 運営
27 分で読めます

みなさん、こんな経験はありませんか?

「Reactアプリの初期表示が遅すぎる」
「ページを開くまでに白い画面が長時間表示される」
「ユーザーが離脱してしまう」

アプリケーションが大きくなるにつれて、読み込み時間が長くなるのはよくある悩みです。 でも、React.lazyを使えば、この問題をスッキリ解決できますよ!

この記事では、React.lazyを使った遅延読み込みの方法を、基本から実践まで詳しく解説します。 アプリの表示速度を劇的に改善して、ユーザーに快適な体験を提供しましょう。

React.lazyって何?どうして速くなるの?

React.lazyは、コンポーネントを必要になった時だけ読み込む機能です。 簡単に言うと、使わない部分は後回しにできるんです。

普通の方法と何が違うの?

まずは、従来の方法と比較してみましょう。

❌ 従来の方法(全部最初に読み込み)

import Header from './components/Header';
import Sidebar from './components/Sidebar';
import Dashboard from './components/Dashboard';
import Settings from './components/Settings';

function App() {
  const [currentPage, setCurrentPage] = useState('dashboard');

  return (
    <div>
      <Header />
      <Sidebar />
      {currentPage === 'dashboard' && <Dashboard />}
      {currentPage === 'settings' && <Settings />}
    </div>
  );
}

この書き方だと、Settingsページを見ない人にも、Settingsコンポーネントが読み込まれてしまいます。 無駄ですよね。

✅ React.lazyを使った方法(必要な時だけ読み込み)

import React, { Suspense, lazy, useState } from 'react';
import Header from './components/Header';
import Sidebar from './components/Sidebar';

// 必要な時だけ読み込まれる
const Dashboard = lazy(() => import('./components/Dashboard'));
const Settings = lazy(() => import('./components/Settings'));

function App() {
  const [currentPage, setCurrentPage] = useState('dashboard');

  return (
    <div>
      <Header />
      <Sidebar />
      <Suspense fallback={<div>読み込み中...</div>}>
        {currentPage === 'dashboard' && <Dashboard />}
        {currentPage === 'settings' && <Settings />}
      </Suspense>
    </div>
  );
}

この方法なら、Dashboardを表示する時にDashboardだけが読み込まれます。 結果として、初期表示がずっと速くなるんです。

React.lazyの基本的な書き方

React.lazyの使い方はとてもシンプルです。

// 1. lazy関数でコンポーネントを包む
const LazyComponent = lazy(() => import('./LazyComponent'));

// 2. Suspenseで囲んで使う
function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

重要なのは、lazyで読み込んだコンポーネントは必ずSuspenseで囲むことです。 これを忘れるとエラーになってしまいます。

Suspenseって何をしてくれるの?

Suspenseは、遅延読み込み中のローディング状態を管理してくれます。 コンポーネントが読み込まれるまでの間、何を表示するかを決められるんです。

基本的なSuspenseの使い方

import React, { Suspense, lazy } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <div>
      <h1>メインコンテンツ</h1>
      
      <Suspense fallback={<div>コンポーネントを読み込み中...</div>}>
        <HeavyComponent />
      </Suspense>
    </div>
  );
}

fallbackで指定した内容が、読み込み中に表示されます。 シンプルなテキストでも、凝ったアニメーションでも何でもOKです。

複数のコンポーネントをまとめて管理

一つのSuspenseで、複数のコンポーネントを管理することもできます。

function App() {
  const [showComponents, setShowComponents] = useState(false);

  return (
    <div>
      <button onClick={() => setShowComponents(!showComponents)}>
        コンポーネント切り替え
      </button>
      
      {showComponents && (
        <Suspense fallback={<div>読み込み中...</div>}>
          <LazyComponent1 />
          <LazyComponent2 />
          <LazyComponent3 />
        </Suspense>
      )}
    </div>
  );
}

この場合、どれか一つでも読み込み中なら、fallbackが表示されます。

おしゃれなローディング画面を作ろう

fallbackには、どんなコンポーネントでも指定できます。

const LoadingSpinner = () => (
  <div className="loading-container">
    <div className="spinner"></div>
    <p>コンテンツを準備中...</p>
  </div>
);

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <LazyComponent />
    </Suspense>
  );
}

ユーザーに「ちゃんと読み込んでますよ」と伝えることで、離脱を防げます。

CSSでアニメーションを加えれば、さらに魅力的になりますね。

.spinner {
  border: 3px solid #f3f3f3;
  border-top: 3px solid #3498db;
  border-radius: 50%;
  width: 30px;
  height: 30px;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

実際のアプリで使ってみよう

理論はわかったところで、実際のアプリケーションでどう使うか見てみましょう。 よくあるケースを例に説明しますね。

ページ切り替えで遅延読み込み

React Routerと組み合わせると、ページごとに必要なコンポーネントだけを読み込めます。

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';

// ページコンポーネントを遅延読み込み
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {
  return (
    <Router>
      <div>
        <nav>
          <Link to="/">ホーム</Link>
          <Link to="/about">アバウト</Link>
          <Link to="/contact">お問い合わせ</Link>
          <Link to="/dashboard">ダッシュボード</Link>
        </nav>
        
        <Suspense fallback={<div>ページを読み込み中...</div>}>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            <Route path="/contact" element={<Contact />} />
            <Route path="/dashboard" element={<Dashboard />} />
          </Routes>
        </Suspense>
      </div>
    </Router>
  );
}

これで、ユーザーが訪れたページだけが読み込まれます。 5つのページがあっても、最初はHomeページだけ読み込まれるので、初期表示が速くなります。

条件に応じた遅延読み込み

管理者のみに表示される機能など、条件付きで表示されるコンポーネントも遅延読み込みできます。

import React, { Suspense, lazy, useState } from 'react';

const AdminPanel = lazy(() => import('./AdminPanel'));
const UserProfile = lazy(() => import('./UserProfile'));

function App() {
  const [user, setUser] = useState(null);
  const [showAdmin, setShowAdmin] = useState(false);

  const handleLogin = (userData) => {
    setUser(userData);
  };

  return (
    <div>
      {!user ? (
        <LoginForm onLogin={handleLogin} />
      ) : (
        <div>
          <h1>ようこそ、{user.name}さん</h1>
          
          <Suspense fallback={<div>プロフィールを読み込み中...</div>}>
            <UserProfile user={user} />
          </Suspense>
          
          {user.role === 'admin' && (
            <div>
              <button onClick={() => setShowAdmin(!showAdmin)}>
                管理パネル{showAdmin ? '非表示' : '表示'}
              </button>
              
              {showAdmin && (
                <Suspense fallback={<div>管理パネルを読み込み中...</div>}>
                  <AdminPanel />
                </Suspense>
              )}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

この例では、以下のように読み込みが最適化されます。

  1. ログイン前: LoginFormのみ読み込み
  2. 一般ユーザーでログイン: UserProfileのみ追加読み込み
  3. 管理者でボタンクリック: AdminPanelを初めて読み込み

必要最小限の読み込みで、快適な体験を提供できます。

複数のSuspenseで細かく制御

異なる部分に異なるローディング表示をすることもできます。

function App() {
  return (
    <div>
      <header>
        <h1>アプリケーション</h1>
      </header>
      
      <main>
        {/* メインコンテンツ */}
        <Suspense fallback={<div>メインコンテンツを読み込み中...</div>}>
          <MainContent />
        </Suspense>
        
        {/* サイドバー */}
        <aside>
          <Suspense fallback={<div>サイドバーを読み込み中...</div>}>
            <Sidebar />
          </Suspense>
        </aside>
      </main>
      
      {/* フッター */}
      <footer>
        <Suspense fallback={<div>フッターを読み込み中...</div>}>
          <Footer />
        </Suspense>
      </footer>
    </div>
  );
}

このように分けることで、各部分が独立して読み込まれます。 ユーザーは、読み込み完了した部分から順次見ることができるんです。

さらに高速化するテクニック

基本的な使い方ができたら、さらなる最適化に挑戦しましょう。 上級者向けのテクニックをご紹介します。

事前読み込み(プリロード)で体感速度アップ

ユーザーが使いそうなコンポーネントを、事前に読み込んでおく方法です。

import React, { Suspense, lazy, useEffect } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

// プリロード用の関数
const preloadHeavyComponent = () => {
  import('./HeavyComponent');
};

function App() {
  const [showHeavy, setShowHeavy] = useState(false);

  // コンポーネントマウント時にプリロード
  useEffect(() => {
    // 3秒後にプリロード開始
    const timer = setTimeout(preloadHeavyComponent, 3000);
    return () => clearTimeout(timer);
  }, []);

  return (
    <div>
      <button 
        onMouseEnter={preloadHeavyComponent} // ホバー時にプリロード
        onClick={() => setShowHeavy(true)}
      >
        重いコンポーネントを表示
      </button>
      
      {showHeavy && (
        <Suspense fallback={<div>読み込み中...</div>}>
          <HeavyComponent />
        </Suspense>
      )}
    </div>
  );
}

プリロードのタイミング

  1. コンポーネントマウント後: 初期表示が終わったら開始
  2. ホバー時: ボタンにマウスを載せた時
  3. アイドル時: ユーザーが何もしていない時

ユーザーがクリックする頃には、すでに読み込み完了しているので、瞬時に表示されます。

エラーが起きた時の対処法

ネットワークエラーなどで読み込みに失敗した場合の処理も追加しましょう。

import React, { Suspense, lazy } from 'react';

const LazyComponent = lazy(() => import('./LazyComponent'));

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('遅延読み込みエラー:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>コンポーネントの読み込みに失敗しました</h2>
          <button onClick={() => window.location.reload()}>
            ページを再読み込み
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

function App() {
  return (
    <ErrorBoundary>
      <Suspense fallback={<div>読み込み中...</div>}>
        <LazyComponent />
      </Suspense>
    </ErrorBoundary>
  );
}

ErrorBoundaryで囲むことで、読み込みエラーが起きても、アプリ全体がクラッシュすることを防げます。

読み込み時間に応じた表示の変更

読み込みに時間がかかる場合、段階的にメッセージを変える方法もあります。

import React, { Suspense, lazy, useState, useEffect } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function ProgressiveLoader() {
  const [loadingTime, setLoadingTime] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setLoadingTime(prev => prev + 1);
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  if (loadingTime < 2) {
    return <div>読み込み中...</div>;
  } else if (loadingTime < 5) {
    return <div>少々お待ちください...</div>;
  } else {
    return (
      <div>
        <div>読み込みに時間がかかっています</div>
        <div>ネットワーク接続を確認してください</div>
      </div>
    );
  }
}

function App() {
  return (
    <Suspense fallback={<ProgressiveLoader />}>
      <HeavyComponent />
    </Suspense>
  );
}

このように時間に応じて表示を変えることで、ユーザーに適切な情報を提供できます。

表示の流れ

  1. 最初の2秒: 「読み込み中...」
  2. 2〜5秒: 「少々お待ちください...」
  3. 5秒以降: 「時間がかかっています」+「ネットワーク確認」

よくある失敗を避けよう

React.lazyを使い始めると、よくある失敗パターンがあります。 事前に知っておけば、つまずかずに済みますよ。

default exportが必須です

React.lazyは、default exportされたコンポーネントしか読み込めません。

❌ 間違った例(named export)

// MyComponent.js
export const MyComponent = () => <div>Component</div>;

// App.js
const MyComponent = lazy(() => import('./MyComponent')); // エラー!

✅ 正しい例(default export)

// MyComponent.js
const MyComponent = () => <div>Component</div>;
export default MyComponent;

// または
export default function MyComponent() {
  return <div>Component</div>;
}

// App.js
const MyComponent = lazy(() => import('./MyComponent')); // OK!

どうしてもnamed exportを使いたい場合

中間ファイルを作るか、import文を工夫すれば可能です。

// コンポーネントファイル(MyComponent.js)
export const MyComponent = () => <div>Component</div>;
export const OtherComponent = () => <div>Other</div>;

// 使用する側
const MyComponent = lazy(() => 
  import('./MyComponent').then(module => ({ 
    default: module.MyComponent 
  }))
);

.then()でdefault exportに変換しています。 少し複雑ですが、どうしても必要な時に使ってください。

Suspenseで囲み忘れに注意

これは本当によくある間違いです。

❌ エラーが発生する例

const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
  return <LazyComponent />; // Suspenseがない
}
// Error: A React component suspended while rendering...

✅ 正しい実装

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

覚えておくコツ

lazyを使ったら、必ずSuspenseがセットです。 一つ覚えたら、もう一つも覚える習慣をつけましょう。

条件分岐の中でlazyを使わない

フック系と同じで、lazyも条件分岐の中では使えません。

❌ 間違った例

function App() {
  const [condition, setCondition] = useState(false);
  
  if (condition) {
    const LazyComponent = lazy(() => import('./LazyComponent')); // NG
    return <LazyComponent />;
  }
  
  return <div>Default</div>;
}

✅ 正しい例

// コンポーネントレベルでlazyを定義
const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
  const [condition, setCondition] = useState(false);
  
  return (
    <div>
      {condition && (
        <Suspense fallback={<div>Loading...</div>}>
          <LazyComponent />
        </Suspense>
      )}
    </div>
  );
}

lazyの定義は、必ずコンポーネントの外側か、関数の一番上で行いましょう。

import文の書き方にも注意

import文で相対パスを間違えると、読み込みに失敗します。

// ファイル構造
// src/
//   components/
//     LazyComponent.js
//   App.js

// ❌ パスが間違っている
const LazyComponent = lazy(() => import('./LazyComponent')); 

// ✅ 正しいパス
const LazyComponent = lazy(() => import('./components/LazyComponent'));

ファイルの場所を確認して、正確なパスを書きましょう。

まとめ:React.lazyでアプリを高速化しよう!

React.lazyを使った遅延読み込みについて、基本から実践まで詳しく解説しました。

React.lazyの効果

  1. 初期表示の高速化: 必要なコンポーネントだけ読み込み
  2. ユーザー体験向上: 待ち時間の短縮
  3. リソース節約: 使われないコードは読み込まない
  4. SEO改善: ページ速度の向上

使い方のポイント

  1. lazy + Suspense: 必ずセットで使う
  2. default export: コンポーネントの書き方に注意
  3. エラーハンドリング: ErrorBoundaryで安全性を確保
  4. プリロード: 事前読み込みで体感速度アップ

実践のコツ

  • 段階的導入: まずは大きなコンポーネントから始める
  • 測定と改善: 実際の効果を数値で確認
  • ユーザー視点: ローディング表示にも気を配る
  • チーム共有: best practiceをメンバーと共有

特に、ページ数の多いSPAや、重いライブラリを使うコンポーネントでは、劇的な効果が期待できます。

ぜひ今回紹介したテクニックを実際のプロジェクトで試してみてください。 きっと、ユーザーから「アプリが速くなった!」と喜ばれますよ。

まずは小さなコンポーネントから始めて、徐々に範囲を広げていきませんか?

関連記事