【aspida×SWR】useAspidaSWRでフェッチ周りを楽しよう!
はじめに
React
でAPIからデータを取得する時、みなさんはどんな方法を使っていますか?
今回は、型安全なAPIクライアント生成ツール「aspida」と、
データ取得ライブラリ「SWR」を組み合わせたuseAspidaSWRを使った実装を行いましたので、
ご紹介したいと思います!
利用者想定
- React, Nextを用いて開発を行っている方
- フェッチ周りを楽したい方
使うライブラリ
- @aspida/swr
- 既存の
aspida
とSWR
のライブラリが合体したイメージでOKです!
- 既存の
aspida
とSWR
は非常に便利なライブラリです!
ですが、今回の主役は「aspida/swr」ですので、本記事でのaspida
とSWR
の詳細な解説は割愛させていただきます🙏
詳しく知りたい方は、上記リンクをご参照ください!
useAspidaSWRとは?
useAspidaSWR
とは、型安全なAPIクライアントライブラリであるaspida
と
データ取得ライブラリSWR
を組み合わせた便利なHook
です。
元々は自力でaspida
で定義していたフェッチ関数をSWR
でラップしていたんですが、
「これって、もう誰かライブラリ作ってるんじゃね…?」と思って調べてみたところ、まさにその通りでした・・・!
嬉しい発見ですね 😉
useAspidaSWRを使うメリット
1. aspidaとSWRのいいとこ取りができる
- APIのリクエスト・レスポンスに型がつくので、補完が効いてミスが軽減できる
-
SWR
のキャッシュや再取得、フォーカス時のリフェッチ、エラーハンドリングも全部使える
2. コードが読みやすく、扱いやすくなる
- フェッチ処理やローディング、エラーハンドリングが1つのHookにまとまるからスッキリ
- 状態が分散しないので、処理の流れが追いやすくて読みやすい
- 無駄なコードが減って、保守もしやすくなる。チームで開発する際も理解コストが下がる
3. 学習コストが低く、すぐ使いこなせる
-
aspida
とSWR
はどちらも シンプルな構成でドキュメントもわかりやすい(個人的な主観) - 既存のREST APIとも相性が良く、導入ハードルは低い
useAspidaSWR に渡せるオプション一覧
個人的によく使うもの
オプション名 | 型 | 説明 |
---|---|---|
query |
Record<string, any> |
クエリパラメータを指定(例:{ id: 123 } → /api/users?id=123 ) |
headers |
Record<string, string> |
リクエストに任意の HTTP ヘッダーを付与(例:認証トークンなど) |
key |
string |
SWR のキャッシュキーを手動で指定したい場合に使用 |
errorRetryCount |
number |
最大リトライ回数 |
revalidateOnMount |
boolean |
コンポーネントのマウント時に再フェッチするか |
shouldRetryOnError |
boolean or function
|
エラー時に再試行するか(条件付きも可) |
onSuccess |
(data, key, config) => void |
フェッチ成功時のコールバック |
onError |
(err, key, config) => void |
エラー時のコールバック |
その他
オプション名 | 型 | 説明 |
---|---|---|
errorRetryInterval |
number |
エラー時の再試行間隔(ミリ秒) |
loadingTimeout |
number |
ローディングが遅いときに onLoadingSlow を発火するまでの時間 |
focusThrottleInterval |
number |
フォーカス時の再検証を抑制する間隔(ミリ秒) |
dedupingInterval |
number |
同じキーでのリクエストをまとめる間隔(ミリ秒) |
refreshInterval |
number or function
|
ポーリング間隔(ミリ秒)または関数で動的に設定 |
refreshWhenHidden |
boolean |
タブが非表示状態でもポーリングを続けるか |
refreshWhenOffline |
boolean |
オフライン時でもポーリングを続けるか |
revalidateOnFocus |
boolean |
タブにフォーカスされたときに再フェッチするか |
revalidateOnReconnect |
boolean |
ネットワーク復帰時に再フェッチするか |
revalidateIfStale |
boolean |
データが古い場合でも再フェッチするか |
keepPreviousData |
boolean |
キーが変更されても前回のデータを保持するか |
suspense |
boolean |
React Suspense モードを有効にするか |
fallbackData |
any |
初回表示時に使用する仮データ(1 Hook 単位) |
use |
Middleware[] |
カスタムミドルウェア配列 |
fallback |
{ [key: string]: any } |
複数のキーに対する初期データマップ |
isPaused |
() => boolean |
true を返すとフェッチを一時停止できる |
onLoadingSlow |
(key, config) => void |
フェッチが遅いときのコールバック |
onErrorRetry |
(err, key, config, revalidate) |
エラー時の再試行戦略を定義するコールバック |
onDiscarded |
(key) => void |
リクエストが破棄されたときのコールバック |
compare |
(a, b) => boolean |
データ変更検出に使う比較関数 |
isOnline |
() => boolean |
オンライン状態かを判定する関数 |
isVisible |
() => boolean |
タブが表示状態かを判定する関数 |
実装例
1. 導入
公式Doc通りに下記のコマンドを実行して、ライブラリをインストールしましょう
bun add aspida @aspida/swr @aspida/axios swr axios
2. aspidaでAPIを定義する
まずはapi
ディレクトリを作成し、そこにAPI定義ファイルを作っていきます。
mkdir api
api
ディレクトリ配下にAPIの型定義を作成します。
// api/users/index.tsに作成する
export type Methods = {
get: {
resBody: {
id: number
name: string
}[]
}
}
手動で aspida.config.js を作成してファイルベースからクライアントコードを自動生成できるようにします。
// aspida.config.js の例
module.exports = {
input: 'api', // API 定義ファイルを置くディレクトリ
baseURL: 'https://your-api.example.com' // オプション:生成されるクライアントのベースURL
}
// package.jsonに"aspida"を追加
"scripts": {
"aspida": "aspida -c aspida.config.cjs",
...
},
aspidaの基本的な設定は以上です! ✨
これで下記のコマンドを実行することで、api/$api.ts
や'api/$path.ts'が生成されます!
# APIを修正・新規追加するたびにコマンドを実行しよう!
bun aspida
これで以下のようにフェッチ処理を実装することができます。
import aspida from '@aspida/axios'
import api from './api/$api'
export const apiClient = api(aspida())
// 使用例
const res = await apiClient.users.get();
console.log(res.body) // {id: "string", name: "string"}
3. フェッチ処理箇所の基本的な書き方
レスポンスデータだけでなく、ローディングフラグやエラー情報も一緒に返してくれるので、フェッチ周りの状態管理がとても楽です!
import useAspidaSWR from '@aspida/swr'
import { apiClient } from '../lib/apiClient'
const Users = () => {
const { data, error, isLoading } = useAspidaSWR(apiClient.users)
if (isLoading) return <p>読み込み中...</p>
if (error) return <p>エラーが発生しました</p>
return (
<ul>
{data?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
ちょっと凝った書き方
オプションを追加することでフェッチに関連する細かな制御が可能です
// ex.プロフィール情報の取得
return useAspidaSWR(
apiClient.user._code(code),
{
headers: {
Authorization: `Bearer ${session?.token}`, // 認証情報
},
key: [FETCH_BUCKET_ITEMS_FOR_USER_KEY, code], // SWR のキャッシュキー(任意で指定可能)
enabled: !!code, // フェッチ実行の有無
errorRetryCount: 0, // エラー時のリトライ回数(0でリトライなし)
revalidateOnFocus: false, // フォーカス時に再フェッチ
revalidateOnReconnect: false, // 再接続時の再フェッチを無効化
shouldRetryOnError: false, // エラー時に再試行するかどうか
dedupingInterval: 100000, // キャッシュの有効期限を設定
onError(error) {
errorHandToast(error);
},
},
);
・・・ 🤔
毎回オプション定義してたら、逆に視認性悪くない?
と思いましたよね?僕も実装時思っていました;;
ですが、グローバルにオプションを一括設定する方法があるのでご安心ください。
グローバルにオプションを一括設定する方法
import { SWRConfig, useSWRConfig } from 'swr'
function App() {
return (
<SWRConfig
value={{
dedupingInterval: 100,
refreshInterval: 100,
fallback: { a: 1, b: 1 },
}}
>
<Page />
</SWRConfig>
)
}
個別にオプションを設定することで、オプションを上書きすることもできます。
まとめ
本記事では、useAspidaSWR
を使って API フェッチ処理をシンプルかつ型安全に行う方法をご紹介しました。
-
aspida
+SWR
の組み合わせで、非同期処理が簡潔に書ける -
data
、error
、isLoading
が1〜2行ほどで扱えるので、状態管理がとっても楽 - 豊富なオプションで、必要に応じたカスタマイズができる
-
SWRConfig
を使えば、グローバルな設定も可能
目的に応じては、aspida
+SWR
もまだまだ開発現場でも採用できる技術かなと思っていますので、ぜひ選択肢の一つとしてご検討してみたはいかがでしょうか!
Discussion