T-CREATOR

React Suspense入門:非同期UIを直感的に書ける新しいアプローチとは?

React Suspense入門:非同期UIを直感的に書ける新しいアプローチとは?

Reactで非同期データを扱う際、これまでは useEffectisLoading フラグを駆使して状態管理を行う必要がありました。しかし、それではコードが複雑になりがちで、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 Componentsuse() などとも統合されていく

Suspenseを上手く活用することで、よりシンプルで保守性の高い非同期UI設計が可能になります。ぜひ一度試してみてください!

📘 参考リンクまとめ:

記事Article

もっと見る