🌏

【aspida×SWR】useAspidaSWRでフェッチ周りを楽しよう!

に公開

はじめに

ReactでAPIからデータを取得する時、みなさんはどんな方法を使っていますか?

今回は、型安全なAPIクライアント生成ツール「aspida」と、
データ取得ライブラリ「SWR」を組み合わせたuseAspidaSWRを使った実装を行いましたので、
ご紹介したいと思います!

利用者想定

  • React, Nextを用いて開発を行っている方
  • フェッチ周りを楽したい方

使うライブラリ

  • @aspida/swr
    • 既存のaspidaSWRのライブラリが合体したイメージでOKです!

https://www.npmjs.com/package/@aspida/swr

https://www.npmjs.com/package/@aspida/fetch

https://swr.vercel.app/ja

aspidaSWRは非常に便利なライブラリです!
ですが、今回の主役は「aspida/swr」ですので、本記事でのaspidaSWRの詳細な解説は割愛させていただきます🙏
詳しく知りたい方は、上記リンクをご参照ください!

useAspidaSWRとは?

useAspidaSWRとは、型安全なAPIクライアントライブラリであるaspida
データ取得ライブラリSWRを組み合わせた便利なHookです。

元々は自力でaspidaで定義していたフェッチ関数をSWRでラップしていたんですが、
「これって、もう誰かライブラリ作ってるんじゃね…?」と思って調べてみたところ、まさにその通りでした・・・!

嬉しい発見ですね 😉

useAspidaSWRを使うメリット

1. aspidaとSWRのいいとこ取りができる

  • APIのリクエスト・レスポンスに型がつくので、補完が効いてミスが軽減できる
  • SWRのキャッシュや再取得、フォーカス時のリフェッチ、エラーハンドリングも全部使える

2. コードが読みやすく、扱いやすくなる

  • フェッチ処理やローディング、エラーハンドリングが1つのHookにまとまるからスッキリ
  • 状態が分散しないので、処理の流れが追いやすくて読みやすい
  • 無駄なコードが減って、保守もしやすくなる。チームで開発する際も理解コストが下がる

3. 学習コストが低く、すぐ使いこなせる

  • aspidaSWRはどちらも シンプルな構成でドキュメントもわかりやすい(個人的な主観)
  • 既存の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>
  )
}

個別にオプションを設定することで、オプションを上書きすることもできます。

https://swr.vercel.app/ja/docs/global-configuration

まとめ

本記事では、useAspidaSWRを使って API フェッチ処理をシンプルかつ型安全に行う方法をご紹介しました。

  • aspida+SWRの組み合わせで、非同期処理が簡潔に書ける
  • dataerrorisLoadingが1〜2行ほどで扱えるので、状態管理がとっても楽
  • 豊富なオプションで、必要に応じたカスタマイズができる
  • SWRConfig を使えば、グローバルな設定も可能

目的に応じては、aspida+SWRもまだまだ開発現場でも採用できる技術かなと思っていますので、ぜひ選択肢の一つとしてご検討してみたはいかがでしょうか!

Discussion