はじめに
こんにちは、都内でソフトウェアエンジニアをしているYSasagoと申します。
React18で、Suspense機能が追加されました。そこで、Suspense機能についてまとめようかと思います。
- Suspense機能を使うことで、API実行時などのローディング状態の記述を上位のコンポーネントで宣言できる
- Suspense機能を適切に分割して使うことでユーザービリティの向上をつなげることができる
サスペンスにより、コンポーネントツリーの一部がまだ表示できない場合に、ロード中という状態を宣言的に記述できるようになります:
サンプルアプリの作成
Suspense機能を試すために、jsonplaceholderのalbumsを取得して、画面に表示するサンプルアプリを作成しました。
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import reportWebVitals from "./reportWebVitals";
const queyClient = new QueryClient();
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<QueryClientProvider client={queyClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
);
reportWebVitals();
import "./App.css";
import { ReactQuery } from "./components/ReactQuery";
function App() {
return (
<div className="App">
<ReactQuery />
</div>
);
}
export default App;
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
type Album = {
userId: number;
id: number;
title: string;
};
const fetchAlbums = async () => {
const result = await axios.get<Album[]>(
"https://jsonplaceholder.typicode.com/albums"
);
return result.data;
};
export const ReactQuery = () => {
const { isLoading, error, data } = useQuery<Album[]>(["albums"], fetchAlbums);
if (error) return <p>error</p>;
if (isLoading) return <p>Loading...</p>;
return (
<div>
<p>ReactQuery</p>
{data?.map((album) => (
<p key={album.id}>{album.title}</p>
))}
</div>
);
};
読込中の画面表示を、Suspense機能を使って実装してみる
useQueryのisLoadingでローディング画面を実装していた箇所を、Suspense機能を使うように変更します。
react-queryのライブラリは、Suspense機能に対応しているので、有効化するだけで使用することができます。
import { Suspense } from "react"; // 追加
import "./App.css";
import { ReactQuery } from "./components/ReactQuery";
function App() {
return (
<div className="App">
{/* fallbackにはローディング中に表示する画面を渡す*/}
<Suspense fallback={<p>Loading...</p>}>
<ReactQuery />
</Suspense>
</div>
);
}
export default App;
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
type Album = {
userId: number;
id: number;
title: string;
};
const fetchAlbums = async () => {
const result = await axios.get<Album[]>(
"https://jsonplaceholder.typicode.com/albums"
);
return result.data;
};
export const ReactQuery = () => {
const { isLoading, error, data } = useQuery<Album[]>(
["albums"],
fetchAlbums,
{ suspense: true } // 追加: Suspense機能を有効化する
);
// if (error) return <p>error</p>;
// if (isLoading) return <p>Loading...</p>;
return (
<div>
<p>ReactQuery</p>
{data?.map((album) => (
<p key={album.id}>{album.title}</p>
))}
</div>
);
};
Suspense機能を使ってローディング画面を作成することができました。
エラーの画面表示を、Suspense機能を使って実装してみる
エラーの画面表示を、Suspense機能を使って実装しようと思います。
最初に、ライブラリのreact-error-boundaryを追加します。
$ yarn add react-error-boundary
import { Suspense } from "react";
import "./App.css";
import { ReactQuery } from "./components/ReactQuery";
import { ErrorBoundary } from "react-error-boundary"; // 追加
function App() {
return (
<div className="App">
{/* fallbackにエラー画面のUIを渡してあげる */}
<ErrorBoundary fallback={<p>error</p>}>
<Suspense fallback={<p>Loading...</p>}>
<ReactQuery />
</Suspense>
</ErrorBoundary>
</div>
);
}
export default App;
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
type Album = {
userId: number;
id: number;
title: string;
};
const fetchAlbums = async () => {
const result = await axios.get<Album[]>(
"https://jsonplaceholder.typicode.com/albumsあ" // エラーが表示されるように無効なURLを指定する
);
return result.data;
};
export const ReactQuery = () => {
const { data } = useQuery<Album[]>(["albums"], fetchAlbums);
return (
<div>
<p>ReactQuery</p>
{data?.map((album) => (
<p key={album.id}>{album.title}</p>
))}
</div>
);
};
複数のSuspenseを使う場合
複数のSuspenseを使って、書くとユーザービリティの向上につながります。
よって、下記のコードの様にコンポーネントごとに、適切にSuspense
とErrorBoundary
と書くと良いでしょう。
export const ReactQuery = () => {
return (
<div style={{ display: "flex", padding: "16px" }}>
<Sidebar />
<div style={{ flexGrow: 1 }}>
{/* コンポーネント毎にErrorBoundaryとSuspenseを使う */}
<ErrorBoundary fallback={<p>Album list error</p>}>
<Suspense fallback={<p>Album list Loading...</p>}>
<AlbumList />
</Suspense>
</ErrorBoundary>
{/* コンポーネント毎にErrorBoundaryとSuspenseを使う */}
<ErrorBoundary fallback={<p>Todo list error</p>}>
<Suspense fallback={<p>Todo list Loading...</p>}>
<TodoList />
</Suspense>
</ErrorBoundary>
</div>
</div>
);
};
複数のSuspenseを使う場合は、ネストが深いものが最優先されます。
まとめ
- React18で追加されたSuspense機能を手元で動かしてみました
- Suspense機能を導入するとはユーザービリティの向上につながるので、プロジェクトに合わせて導入したいですね
参考