T-CREATOR

ReactとSuspenseで構築する柔軟な非同期UIの設計法について紹介

ReactとSuspenseで構築する柔軟な非同期UIの設計法について紹介

現代のフロントエンドアプリケーションでは、非同期処理とその制御が極めて重要です。 特にユーザー体験を向上させるためには、ローディング、エラーハンドリング、そしてデータ取得のタイミングを慎重に設計する必要があります。 ReactのSuspenseは、こうした課題に対して非常に柔軟かつ強力なソリューションを提供します。

本記事では、ReactとSuspenseを活用した非同期UI設計について、丁寧に、かつ実践的なコード例とともに詳しく解説してまいります。


Suspenseの基本的な仕組み

Suspenseは、非同期コンポーネントのローディング状態を制御するための仕組みです。 React 18以降では、データ取得やコード分割といった非同期処理において、本格的に利用可能となっています。

基本構文とローディングUIの指定

以下に、最もシンプルなSuspenseの使い方を紹介します:

tsximport React, { Suspense } from 'react';

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

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

この例では、LazyComponentが読み込まれるまで、fallbackに指定したローディングUIが表示されます。

コードスプリッティングとの連携

React.lazyは、モジュールを非同期に読み込む手段で、React公式でも推奨されています。

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

データ取得におけるSuspenseの活用

React 18以降、useフックとともにSuspenseを使うことで、データ取得を待ってからUIを描画する設計が可能になりました。

非同期関数とReactのuse関数

tsx// fetchData.ts
export const fetchUser = async (id: string) => {
  const res = await fetch(`/api/user/${id}`);
  if (!res.ok) throw new Error('データ取得に失敗しました');
  return res.json();
};
tsx// UserProfile.tsx
import { use } from 'react';
import { fetchUser } from './fetchData';

export const UserProfile = ({ userId }: { userId: string }) => {
  const user = use(fetchUser(userId));

  return <div>{user.name}</div>;
};

この例では、useフックによってPromiseの解決を待機し、結果を同期的に扱えるようになっています。


サスペンド可能なリソースの自作とキャッシュ制御

Suspenseでデータ取得を完全に制御するためには、リソースのラッパー関数を作成するのが有効です。

ラッパー関数の作成

tsx// createResource.ts
export const createResource = <T>(asyncFunc: () => Promise<T>) => {
  let status = 'pending';
  let result: T;
  let suspender = asyncFunc().then(
    (r) => {
      status = 'success';
      result = r;
    },
    (e) => {
      status = 'error';
      result = e;
    }
  );

  return {
    read(): T {
      if (status === 'pending') {
        throw suspender;
      } else if (status === 'error') {
        throw result;
      } else {
        return result;
      }
    },
  };
};

利用側のコード

tsxconst userResource = createResource(() => fetchUser('123'));

export const Profile = () => {
  const user = userResource.read();

  return <div>{user.name}</div>;
};

このようにすることで、リソースがまだ読み込み中であればSuspenseに制御を移譲できます。


エラーハンドリングとErrorBoundary

非同期UIで特に重要なのが、エラー時の制御です。 Suspenseと併用することで、エラー時にフォールバックUIを提供可能です。

ErrorBoundaryの実装

tsximport React from 'react';

class ErrorBoundary extends React.Component {
  state = { hasError: false };

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

  componentDidCatch(error: Error, info: React.ErrorInfo) {
    console.error('エラー情報:', error, info);
  }

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

    return this.props.children;
  }
}

export default ErrorBoundary;

Suspenseと組み合わせる

tsx<ErrorBoundary>
  <Suspense fallback={<div>読み込み中です...</div>}>
    <Profile />
  </Suspense>
</ErrorBoundary>

この構成により、読み込み中もエラー時も適切なUIを表示できます。


より複雑な構成と複数のSuspense

実際のアプリケーションでは、複数のデータソースを扱うケースがほとんどです。 Suspenseはネストして使うことができ、柔軟に構成を組むことが可能です。

tsx<Suspense fallback={<div>ユーザー情報を読み込み中...</div>}>
  <UserProfile userId="1" />
  <Suspense fallback={<div>通知を読み込み中...</div>}>
    <UserNotifications userId="1" />
  </Suspense>
</Suspense>

このように、個別のコンポーネント単位でローディングUIを細分化できます。


サーバーコンポーネントとの組み合わせ

Next.js 13以降では、React Server ComponentsとSuspenseの組み合わせが可能になり、初期表示の最適化が実現できます。

tsx// app/page.tsx (Next.js 13)
import { Suspense } from 'react';
import UserProfile from './UserProfile';

export default function Page() {
  return (
    <div>
      <h1>ダッシュボード</h1>
      <Suspense fallback={<div>読み込み中...</div>}>
        <UserProfile userId="123" />
      </Suspense>
    </div>
  );
}

この構成は、パフォーマンスとUXの両立を図るうえで極めて効果的です。


まとめ

Suspenseは、非同期UIの設計を大幅に簡潔かつ明快にします。 従来のuseEffectとローカル状態によるローディング制御に比べて、より宣言的で可読性が高く、エラー処理とも自然に統合可能です。

今後はReactのコアエコシステムにおいて、さらに重要な役割を担っていくことが予想されます。

以下に本記事のポイントを整理します:

機能内容
SuspenseローディングUIを制御する仕組み
React.lazy動的インポートによるコード分割
use(fetch())React 18以降の非同期取得の同期的処理
createResourceカスタムリソースラッパーでSuspenseに対応
ErrorBoundaryエラーハンドリング用のコンポーネント
ネスト個別のローディングUI制御に柔軟対応
Next.jsとの統合Server Componentsと組み合わせた最適化

今後の開発において、より洗練された非同期体験を提供するために、Suspenseを活用してみてください。

記事Article

もっと見る