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
React vs Preact 2025 年版 - パフォーマンス・機能・エコシステム完全比較
- article
Motion(旧 Framer Motion)入門:React アニメーションを最速で始める
- article
Web Components と React/Vue.js - 何が違うのか徹底比較
- article
React で SVG アニメーションを自在に操る!2025 年最新サンプル集
- article
アクセシビリティ重視!React アニメーションと WCAG 対応の勘所
- article
フェード・ズーム・スライド全部見せます!React で人気アニメーション完全網羅
- article
Zustand × Suspense:データ取得を直感的に扱う設計パターン
- article
React 18 × Jotai 完全対応ガイド - Suspense との連携方法
- article
React × Suspenseを組み合わせてスケーラブルな非同期UIを実現する方法
- article
Next.js × Suspenseを活用した非同期ルーティング体験を実現する方法
- article
React SuspenseでUIを設計する際に避けたいアンチパターンと解決策
- article
React Suspenseでデータフェッチ!fetchでは動かない理由と正しい使い方
- article
生成 AI 時代の新常識!GPT-5 のセキュリティ・倫理・安全設計の最新動向
- article
【実践】NestJS で REST API を構築する基本的な流れ
- article
TypeScript × GitHub Copilot:型情報を活かした高精度コーディング
- article
Motion(旧 Framer Motion)Variants 完全攻略:staggerChildren・when で複雑アニメを整理する
- article
JavaScript のオブジェクト操作まとめ:Object.keys/entries/values の使い方
- article
GitHub Actions で CI/CD パイプラインを構築する方法
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- blog
失敗を称賛する文化はどう作る?アジャイルな組織へ生まれ変わるための第一歩
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来