React Suspenseとは?非同期UIを直感的に!基本と使い方
Reactで非同期データを扱う際、これまでは useEffect や isLoading フラグを駆使して状態管理を行う必要がありました。しかし、それではコードが複雑になりがちで、UIとロジックが分離しにくく、読みやすさや再利用性にも課題がありました。
React 18から正式にサポートされた Suspense は、非同期UIに対する新しいアプローチを提供します。本記事では、React初心者の方にもわかりやすいように、Suspenseの基本から応用までを丁寧に解説いたします。
Suspenseとは?
Suspense は、Reactコンポーネントが「まだ準備できていない」状態を表現するための仕組みです。
たとえば、APIからのデータ取得中や、動的に読み込んでいるコンポーネントがまだロードされていないときに、一時的にフォールバックUI(読み込み中の表示など)を表示して、ユーザー体験を損なわないようにできます。
📘 公式ドキュメント:
React – Suspense
なぜSuspenseが必要なのか
従来の書き方では以下のようなコードがよくありました。
tsxconst MyComponent = () => {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
fetch("/api/data")
.then((res) => res.json())
.then((json) => {
setData(json)
setLoading(false)
})
}, [])
if (loading) return <p>Loading...</p>
return <div>{data.title}</div>
}
このようなコードは状態が増えるたびに複雑になり、テストや保守が困難になります。Suspenseを使えば、「待機中の状態」をUIで自然に表現できます。
基本的な使い方
まずは一番基本的な使い方を見てみましょう。
tsximport React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>読み込み中です...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
解説:
React.lazy()でコンポーネントを動的に読み込みます(コード分割)。Suspenseコンポーネントでその読み込みをラップし、fallbackプロパティで一時的に表示するUIを指定します。
React.lazyとの組み合わせ
React.lazy は コンポーネントの遅延読み込み(code-splitting) を実現するAPIです。
例えば、ルーティングと組み合わせてページ単位で分割できます。
tsxconst AboutPage = React.lazy(() => import('./pages/About'));
<Suspense fallback={<p>読み込み中...</p>}>
<AboutPage />
</Suspense>
📘 公式ドキュメント:
Code-Splitting – React
データフェッチとSuspense
React 18以降では、データの取得処理にもSuspenseが使えるようになってきています。
✅ 例:wrapPromise を使ったフェッチ
tsxfunction wrapPromise<T>(promise: Promise<T>) {
let status = 'pending'
let result: T
let suspender = promise.then(
(r) => {
status = 'success'
result = r
},
(e) => {
status = 'error'
result = e
}
)
return {
read() {
if (status === 'pending') throw suspender
if (status === 'error') throw result
return result
}
}
}
実際に使う例:
tsxconst resource = wrapPromise(fetch("/api/data").then(res => res.json()))
const DataComponent = () => {
const data = resource.read()
return <div>{data.title}</div>
}
<Suspense fallback={<p>読み込み中...</p>}>
<DataComponent />
</Suspense>
このように read() の中でまだ結果が来ていなければPromiseをthrowし、Suspenseで待機状態に入れる仕組みを使います。
React 18のConcurrent Featuresとの連携
React 18から登場したConcurrent Features(並列レンダリング)と組み合わせることで、より高度な非同期UIが実現可能になります。
例:startTransition の併用
tsximport { startTransition, useState } from 'react';
const SearchComponent = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
startTransition(() => {
// 非同期フィルタ処理など
setResults(fakeSearch(value));
});
setQuery(value);
};
return (
<>
<input value={query} onChange={onChange} />
<ResultsList results={results} />
</>
);
};
📘 公式ガイド:
Concurrent Features – React
Error Boundaryとの併用
Suspenseで非同期処理を扱う場合、失敗時のハンドリングが必要です。ReactのError Boundary を組み合わせることで、エラーハンドリングが可能になります。
tsxclass ErrorBoundary extends React.Component {
state = { hasError: false }
static getDerivedStateFromError() {
return { hasError: true }
}
render() {
if (this.state.hasError) {
return <h1>エラーが発生しました</h1>
}
return this.props.children
}
}
使用例:
tsx<ErrorBoundary>
<Suspense fallback={<p>読み込み中...</p>}>
<DataComponent />
</Suspense>
</ErrorBoundary>
実践:APIデータ取得とSuspense UIの作成
APIからのデータ取得 → Suspense + カスタムHookで
tsxfunction fetchUser(id: string) {
return wrapPromise(
fetch(`/api/user/${id}`).then((res) => res.json())
);
}
const userResource = fetchUser("123")
const UserInfo = () => {
const user = userResource.read();
return <div>{user.name}さん</div>;
};
const App = () => (
<Suspense fallback={<p>ユーザー情報を取得中です...</p>}>
<UserInfo />
</Suspense>
);
今後の展望と注意点
Suspenseはまだすべてのデータフェッチ手法に完全対応しているわけではありません。特に、外部状態管理(Reduxなど)との相互運用には制限があります。
今後は use() フックや fetch() に直接対応した新しいAPIが開発中です(React Server Components など)。
📘 React 公式の今後のロードマップ:
React Docs – Future of Suspense
まとめ
React Suspenseは、非同期UIをより直感的で宣言的に記述できる強力な仕組みです。まだ発展途上の部分もありますが、以下の点を押さえておくと良いでしょう:
React.lazyと組み合わせてコード分割ができるwrapPromiseパターンでデータ取得もSuspense対応できるErrorBoundaryを併用することで堅牢なエラーハンドリングが可能- 今後は
React Server Componentsやuse()などとも統合されていく
Suspenseを上手く活用することで、よりシンプルで保守性の高い非同期UI設計が可能になります。ぜひ一度試してみてください!
📘 参考リンクまとめ:
articleReact クリーンアーキテクチャ実践:UI・アプリ・ドメイン・データの責務分離
articleReact フック完全チートシート:useState から useTransition まで用途別早見表
articleReact 開発環境の作り方:Vite + TypeScript + ESLint + Prettier 完全セットアップ
articleReact とは? 2025 年版の特徴・強み・実務活用を一気に理解する完全解説
articleESLint を Yarn + TypeScript + React でゼロから構築:Flat Config 完全手順(macOS)
article【徹底比較】Preact vs React 2025:バンドル・FPS・メモリ・DX を総合評価
articleZustand × Suspense:データ取得を直感的に扱う設計パターン
articleReact 18 × Jotai 完全対応ガイド - Suspense との連携方法
articleReact × Suspenseを組み合わせてスケーラブルな非同期UIを実現する方法
articleNext.js × Suspenseを活用した非同期ルーティング体験を実現する方法
articleReact SuspenseでUIを設計する際に避けたいアンチパターンと解決策
articleReact Suspenseでデータフェッチ!fetchでは動かない理由と正しい使い方
articleAstro でレイアウト崩れが起きる原因を特定する手順:スロット/スコープ/スタイル隔離
articleESLint × Vitest/Playwright:テスト環境のグローバルと型を正しく設定
articleDify を Kubernetes にデプロイ:Helm とスケーリング設計の実践
articleApollo のキャッシュ思想を俯瞰する:正規化・型ポリシー・部分データの取り扱い
articleZod で“境界”を守る設計思想:IO バリデーションと型推論の二刀流
articleYarn vs npm vs pnpm 徹底比較:速度・メモリ・ディスク・再現性を実測
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来