T-CREATOR

Suspense × lazyで始めるコード分割:Reactアプリの初歩的最適化

Suspense × lazyで始めるコード分割:Reactアプリの初歩的最適化

Reactで開発を行っていると、アプリケーションの成長とともに、初期表示のパフォーマンスやユーザー体験に関する課題が表面化してきます。

そのような課題の一つに「初期読み込みの遅さ」があります。 Reactでは、この問題を解決するための仕組みとして、React.lazySuspense が提供されています。

本記事では、これらを用いたコード分割の基本的な考え方と、実装方法について、初心者の方でも理解できるように、豊富なサンプルコードとともに解説いたします。

はじめに:コード分割とは?

コード分割(Code Splitting)とは、アプリケーション全体のJavaScriptコードを機能ごとに小さなチャンクに分けて、必要になったときだけ読み込む技術です。

メリット:

  • 初期読み込みサイズが小さくなり、表示が速くなる
  • ユーザーが実際に利用する機能だけを読み込めるため、無駄がない

Reactでは、この機能を実現するための標準的な手法として React.lazySuspense が提供されています。

参考:

基本的な使い方

1. React.lazy の基本構文

tsxconst LazyComponent = React.lazy(() => import('./MyComponent'));

このように書くことで、MyComponent必要になるまで読み込まれない遅延読み込みコンポーネントになります。

2. Suspense を使ったフォールバック表示

React.lazyで読み込んだコンポーネントは、読み込みが完了するまでに時間がかかるため、その間に表示する**フォールバック(代替表示)**を指定する必要があります。

tsximport React, { Suspense } from 'react';

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

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

このように、<Suspense>でラップすることで、読み込み中に "読み込み中..." が表示され、コンポーネントが読み込まれた後に LazyComponent が描画されます。

よくある実装パターン

パターン1:ページごとに分割する

Next.jsでは自動でコード分割されますが、SPAの場合は手動で以下のように実装します。

tsxconst HomePage = React.lazy(() => import('./pages/Home'));
const AboutPage = React.lazy(() => import('./pages/About'));

function Router({ path }: { path: string }) {
  return (
    <Suspense fallback={<div>ページを読み込み中です...</div>}>
      {path === '/' && <HomePage />}
      {path === '/about' && <AboutPage />}
    </Suspense>
  );
}

パターン2:モーダルやダイアログなど、使用頻度の低いUIパーツを遅延読み込み

tsxconst LazyModal = React.lazy(() => import('./components/Modal'));

function App() {
  const [isOpen, setIsOpen] = React.useState(false);

  return (
    <div>
      <button onClick={() => setIsOpen(true)}>モーダルを開く</button>
      {isOpen && (
        <Suspense fallback={<div>モーダルを読み込み中...</div>}>
          <LazyModal onClose={() => setIsOpen(false)} />
        </Suspense>
      )}
    </div>
  );
}

このようにすることで、実際に開かれるときに初めてモーダルを読み込むようになります。

注意点とベストプラクティス

1. Suspenseは単一コンポーネントに対して複数設置可能

必要な箇所ごとに <Suspense> を使うことで、読み込みの粒度を細かく制御できます。

tsx<Suspense fallback={<Loader1 />}>
  <Component1 />
</Suspense>
<Suspense fallback={<Loader2 />}>
  <Component2 />
</Suspense>

2. エラーハンドリングには ErrorBoundary を併用

React.lazyで読み込んだコンポーネントが失敗する場合のために、ErrorBoundaryを使って保険をかけておくのがベストです。

公式参考:

tsxclass ErrorBoundary extends React.Component<any, { hasError: boolean }> {
  constructor(props: any) {
    super(props);
    this.state = { hasError: false };
  }

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

  render() {
    if (this.state.hasError) {
      return <h1>エラーが発生しました。</h1>;
    }

    return this.props.children;
  }
}

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

高度な活用:React Routerとの併用

React Router v6以降では、lazy() を使ってルートごとに遅延読み込みが可能です。

参考:

tsximport {
  createBrowserRouter,
  RouterProvider,
  Route,
} from 'react-router-dom';
import { Suspense, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const router = createBrowserRouter([
  {
    path: '/',
    element: (
      <Suspense fallback={<div>読み込み中...</div>}>
        <Home />
      </Suspense>
    ),
  },
  {
    path: '/about',
    element: (
      <Suspense fallback={<div>読み込み中...</div>}>
        <About />
      </Suspense>
    ),
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

まとめ

  • React.lazySuspenseを活用することで、Reactアプリの初期表示速度を改善できます。
  • フォールバックやエラー処理と組み合わせることで、より柔軟かつ安定したUXを実現できます。
  • ページ単位の分割、モーダルなどのオンデマンドUIへの適用など、活用範囲は広いです。
  • React Routerなどルーティングライブラリとも自然に統合できます。

今後は React Server Components などとの併用も視野に入れることで、さらに効率的な開発が可能になります。

コード分割はパフォーマンス改善の第一歩です。ぜひ実際のプロジェクトに取り入れて、Reactのパフォーマンスを最大限に引き出してください。

記事Article

もっと見る