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設計が可能になります。ぜひ一度試してみてください!
📘 参考リンクまとめ: