Open20
(挑戦)swagger-typescript-apiを用いてAPIクライアントを自動生成する
①yamlファイルから自動生成する
- 用意したyamlファイルからAPIの型を自動生成します。
- ./index.yamlから自動生成したファイルをsrc配下に出力する(以下画像参考)
- ./index.yamlから自動生成したファイルをsrc配下に出力する(以下画像参考)
- 以下のコマンドを実行する
npx swagger-typescript-api -p ./index.yaml -o ./src -n testApi.ts
- これにより自動生成が完了。
- ここからAPIクライアントを実装していく。
多分こっから生成された型使ってSWR用に加工してAPIクライアントの雛形作る。
明日やる。寝る。
自動生成されたコードを読み解いていく。
①レスポンスやリクエストの型が自動生成されている。
export interface hogehoge {
...
}
②HttpClientが生成されている。
- HTTPリクエストを送信するためのメソッド。さまざまなリクエストパラメータと設定を受け取り、適切なフォーマットでリクエストを送信するコードが含まれるー!
export class HttpClient<SecurityDataType = unknown> {
public instance: AxiosInstance;
private securityData: SecurityDataType | null = null;
private securityWorker?: ApiConfig<SecurityDataType>['securityWorker'];
private secure?: boolean;
private format?: ResponseType;
③Apiクラスの定義が生成されている。
- このクラスは、HttpClient<SecurityDataType>を継承しており、HTTPリクエストをさらに具体的なエンドポイントごとに簡潔なメソッドとして定義している。
profiles = {
// プロフィール情報取得APIの定義
profilesList: (params: RequestParams = {}) =>
this.request<
ProfileResponse,
BadRequestResponse | UnauthorizedResponse | void | InternalServerErrorResponse
>({
path: `/profiles`,
method: 'GET',
format: 'json',
...params,
}),
// プロフィール更新APIの定義
profilesUpdate: (data: PutProfileRequest, params: RequestParams = {}) =>
this.request<
ProfileResponse,
BadRequestResponse | UnauthorizedResponse | void | InternalServerErrorResponse
>({
path: `/profiles`,
method: 'PUT',
body: data,
type: ContentType.Json,
format: 'json',
...params,
}),
};
- 以上を用いてAPIクライアントコードを書く。
import axios from 'axios';
import { Api } from './Api'; // Apiクラスの定義をインポート
// 新しいApiクライアントのインスタンスを作成
const api = new Api();
// APIを呼び出す例
async function fetchProfile() {
try {
// profilesListメソッドを使用してプロフィール情報を取得
const response = await api.profiles.profilesList();
// レスポンスのデータを取得
const profileData = response.data;
console.log('Profile Data:', profileData);
} catch (error) {
console.error('Error fetching profile:', error);
}
}
async function updateProfile(updatedProfileData) {
try {
// profilesUpdateメソッドを使用してプロフィール情報を更新
const response = await api.profiles.profilesUpdate(updatedProfileData);
// 更新後のプロフィール情報を取得
const updatedProfile = response.data;
console.log('Updated Profile:', updatedProfile);
} catch (error) {
console.error('Error updating profile:', error);
}
}
APIクライアントの雛形を作る SWR使って。
import React from 'react';
import useSWR from 'swr';
import axios from 'axios';
// APIエンドポイントのベースURL
const API_BASE_URL = 'https://api.example.com';
// SWRのフェッチャーメソッドを定義
const fetcher = async (url) => {
const response = await axios.get(url);
return response.data;
};
// SWRを使用してデータを取得するカスタムフック
function useApi(endpoint) {
const url = `${API_BASE_URL}${endpoint}`;
const { data, error } = useSWR(url, fetcher);
return {
data,
isLoading: !error && !data,
isError: error,
};
}
// アプリケーションコンポーネント
function App() {
// プロフィール情報の取得
const profile = useApi('/profiles');
// プロフィール情報を表示
return (
<div>
<h1>Profile Data</h1>
{profile.isLoading && <p>Loading...</p>}
{profile.isError && <p>Error: {profile.isError.message}</p>}
{profile.data && (
<div>
<p>Name: {profile.data.name}</p>
<p>Email: {profile.data.email}</p>
{/* 他のプロフィール情報を表示 */}
</div>
)}
</div>
);
}
export default App;
そもそもSWRって何?
- データ取得と状態管理のためのライブラリ
- SWR(Stale-While-Revalidate0) vercelがサポートしている。vercel色々やっててすごい
コンセプト
- データを「古い状態のままで表示しつつ、再取得を行う」アプローチを取る。最初のデータ取得時にはキャッシュされたデータ(もしあれば)を即座に表示し、同時にバックグラウンドでデータの再取得を行う。
SWR使う手順
- SWRライブラリをインストール
yarn add swr
- SWRフック(通常はuseSWR)を使用して、データ取得と状態管理を行うコンポーネント内でデータ取得ロジックを記述。
- SWRフックにAPIエンドポイントなどのデータソースを指定し、取得したデータや状態情報をコンポーネント内で利用。
SWR まとめ
①始めに
yarn add swr
- fetcher関数を作る
- 例
const fetcher = (...args) => fetch(...args).then(res => res.json())
- クライアントで使用
import useSWR from 'swr'
function Profile () {
const { data, error, isLoading } = useSWR('/api/user/123', fetcher)
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
// データをレンダリングする
return <div>hello {data.name}!</div>
}
再利用を可能にする
- 例えばユーザのデータ取る時
function useUser (id) {
const { data, error, isLoading } = useSWR(`/api/user/${id}`, fetcher)
return {
user: data,
isLoading,
isError: error
}
}
- コンポーネント内に渡す。
function Avatar ({ id }) {
const { user, isLoading, isError } = useUser(id)
if (isLoading) return <Spinner />
if (isError) return <Error />
return <img src={user.avatar} />
}
②API
const { data, error, isLoading, isValidating, mutate } = useSWR(key, fetcher, options)
パラメーター
- useSWRの第1引数「key」
- リクエストのためのユニークなキー文字列(または関数、配列、null )
- useSWRの第2引数「fetcher」
- データをフェッチするための Promise を返す関数
- useSWRの第2引数「option」
- この SWR フックのオプションオブジェクト
返り値
- data: fetcher によって解決された、指定されたキーのデータ(もしくは、ロードされていない場合は undefined)
- error: fetcher によって投げられたエラー (もしくは undefined)
- isLoading: 実行中のリクエストがあり "ロードされたデータ" がない状態。フォールバックのためのデータや keepPreviousData による以前のデータは "ロードされたデータ" ではありません
- isValidating: リクエストまたは再検証の読み込みがある場合
- mutate(data?, options?): キャッシュされたデータを更新する関数 ( 詳細 )
オプション
③データフェッチ
const { data, error } = useSWR(key, fetcher)
- SWR のとても基本的な API 。この fetcher は非同期関数であり、SWR の key を受け取りデータを返す。
axios
import axios from 'axios'
const fetcher = url => axios.get(url).then(res => res.data)
function App () {
const { data, error } = useSWR('/api/data', fetcher)
// ...
}
ってことでaxiosを用いたSWRでのhooksを作っていく。
axiosを用いたSWRのhooks雛形作り
- ①ベースとなるエンドポイント定義する。
- ②SWRの第二引数に入れるfetcherメソッド定義する。
- ③SWRを用いてデータ取得するカスタムフックを定義する。
- ④使いたいコンポーネントにそれぞれ適用する。
①ベースとなるエンドポイントを定義する
- これは最初に読み込まれるファイルに定義する。私はapp routerの以前のnextでの作業だったので_app.tsxに記述する。
// APIエンドポイントのベースURL
const BASE_URL = 'https://api.example.com';
②SWRの第二引数に入れるfetcherを定義する
import axios from 'axios';
import useSWR from 'swr';
import { Api } from './path-to-generated-api'; // 自動生成されたApiクラスのインポート
// Apiクラスのインスタンスを作成
const api = new Api();
// カスタムフックを定義
export const useApi = ({ method, url, token, params }: Props) => {
// データを取得するための関数
const fetcher = async (
method: string,
url?: string,
token?: string,
params?: { [k: string]: string }
) => {
if (!url || !token) {
return;
}
// 自動生成されたApiクラスを使用してデータを取得
const response = await api.request({
method: method,
url: url,
data: params,
headers: token ? { Authorization: `Bearer ${token}` } : {},
});
return response.data;
};
// SWRを使用してデータを取得
const { data, error } = useSWR(
[url, token],
async () => await fetcher(method, url, token, params),
{
onErrorRetry: (error, _key, _config, revalidate, { retryCount }) => {
// 404と401では再試行しない。
if (error.response.status === 404 || error.response.status === 401) {
return;
}
// 再試行は10回までしかできません。
if (retryCount >= 10) {
return;
}
// 5秒後に再試行します。
setTimeout(() => revalidate({ retryCount }), 5000);
},
}
);
return {
data,
isLoading: !error && !data,
isError: error,
};
};
axiosとは?
- node.jsのpromiseベースのhttpクライアント
axiosとSWRを用いたAPIクライアントを作成する
import axios from 'axios';
import useSWR from 'swr';
// ----------------------------------------------------------------------
export default function useApi(endpoint: string) {
const BASE_URL = 'あなたのAPIのURL';
const fetcher = async (url: string) => {
const response = await axios.get(url, {
headers: {
Authorization: `Bearer ${トークン入れて下さい}`,
},
});
return response.data;
};
const url = `${BASE_URL}${endpoint}`;
const { data, error } = useSWR(url, fetcher);
return {
data,
isLoading: !error && !data,
isError: error,
};
}
- useApiをクライアントで呼び出します。
const { data, isError } = useApi('/取得したいAPIのURL');
- これだけでAPIが叩けます!!!
- axiosとswrに感謝
SWRのTypescriptを使ってみる
ドキュメントを読解していく
- SWR は TypeScript に対応しており、型の安全性を使える。
- SWR は key から fetcher の引数の型を推測する。そのため、適切な型を自動的に設定できる。
- keyとfecherは明示的に型を示すこともできる
import useSWR, { Fetcher } from 'swr'
const uid = '<user_id>'
const fetcher: Fetcher<User, string> = (id) => getUserById(id)
const { data } = useSWR(uid, fetcher)
// `data` は `User | undefined` となります。
- デフォルトはanyであるerrorにも型をつけることができる
const { data, error } = useSWR<User, Error>(uid, fetcher);
// `data` は `User | undefined` となります.
// `error` は `Error | undefined` となります.
- dataの型定義は以下
// 🔹 A. 型付きの fetcher を使う:
// `getUser` は `(endpoint: string) => User` になります。
const { data } = useSWR('/api/user', getUser)
// 🔹 B. データ型を指定:
// `fetcher` は 通常 `any` を返します。
const { data } = useSWR<User>('/api/user', fetcher)
SWR | ミューテーションと再検証(fetche)
- SWR はリモートデータとキャッシュデータのミューテーションのために、mutate と useSWRMutation の API を提供している。
- mutate API を使いデータをミューテート
- どんなキーに対してもミューテートできるグローバルのミューテートAPI
- 対応する SWR フックのデータのみミューテートできるバウンドミューテート API
グローバルミューテートAPI
import { useSWRConfig } from "swr"
function App() {
const { mutate } = useSWRConfig()
mutate(key, data, options)
}
- グローバルにimportも可能
import { mutate } from "swr"
function App() {
mutate(key, data, options)
}
バウンドミューテートAPI
- 現在のキーのデータをミューテートする最も簡単な方法
- useSWR に渡された key に対して対応付けられ、data を第一引数として受け取る。
- 機能自体はグローバルな mutate と同じだが、key を引数として指定する必要がない。
import useSWR from 'swr'
function Profile () {
const { data, mutate } = useSWR('/api/user', fetcher)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
// データを更新するために API にリクエストを送信します
await requestUpdateUsername(newName)
// ローカルのデータを即座に更新して再検証(再フェッチ)します
// 注意: useSWR の mutate は key が対応付けられているため、key の指定は必要ありません
mutate({ ...data, name: newName })
}}>Uppercase my name!</button>
</div>
)
}
再検証(fetch)
- 以下のようにmutate(key) (または単にバウンドミューテートの mutate()) をデータの指定なしに呼んだ場合、そのリソースに対して再fetchが走る。
mutate('/api/user')
mutate()
APIパラメータ
key
- useSWR の key と同じです。しかしながら、関数は フィルタ関数 として振る舞います
data
- クライアントキャッシュを更新するためのデータ、またはリモートミューテーションのための非同期関数
options
- 下記のオプションを受け取ります
optimisticData
- クライアントキャッシュを即座に更新するためのデータ、または現在のデータを受け取り新しいクライアントキャッシュデータを返す関数。
revalidate = true
- 非同期の更新処理を完了した後にキャッシュの再検証を行うかどうか
populateCache = true
- リモートミューテーションの結果をキャッシュに書き込むかどうか、またはリモートミューテーションの結果と現在のデータを引数として受け取り、ミューテーションの結果を返す関数
rollbackOnError = true
- リモートミューテーションがエラーだった場合にキャッシュをロールバックするかどうか、または発生したエラーを引数として受け取りロールバックするかどうかの真偽値を返す関数
throwOnError = true
- ミューテートの呼び出しが失敗した場合にエラーを投げるかどうか
返り値
- mutate は data パラメータとして扱われる結果を返す。
- mutate に渡された関数は対応するキャッシュの値としても使われる更新後のデータを返す。
try {
const user = await mutate('/api/user', updateUser(newUser))
} catch (error) {
// ユーザーの更新中に発生したエラーを処理します
}
useSWRMutation
- 基本的な使い方
import useSWRMutation from 'swr/mutation'
async function sendRequest(url, { arg }: { arg: { username: string } }) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(arg)
}).then(res => res.json())
}
function App() {
const { trigger, isMutating } = useSWRMutation('/api/user', sendRequest, /* options */)
return (
<button
disabled={isMutating}
onClick={async () => {
try {
const result = await trigger({ username: 'johndoe' }, /* options */)
} catch (e) {
// エラーハンドリング
}
}}
>
Create User
</button>
)
}
- mutationの結果をレンダリングで使う場合
const { trigger, data, error } = useSWRMutation('/api/user', sendRequest)
- 読み込みの遅延を実装する
import { useState } from 'react'
import useSWRMutation from 'swr/mutation'
const fetcher = url => fetch(url).then(res => res.json())
const Page = () => {
const [show, setShow] = useState(false)
// trigger が呼ばれるまで data は undefined
const { data: user, trigger } = useSWRMutation('/api/user', fetcher);
return (
<div>
<button onClick={() => {
trigger();
setShow(true);
}}>Show User</button>
{show && user ? <div>{user.name}</div> : null}
</div>
);
}
triggerを呼び出して更新処理が可能になる。