openapi-react-queryを使うとAPIを型安全に(しかも簡単に)叩くことができます。
目次
OpenAPIスキーマから型を動的生成する
openapi-typescriptはOpenAPIのスキーマ(json or yaml)をtypescriptの型定義ファイルに変換してくれます。これを使ってAPIの型を自動生成します。
npx openapi-typescript ./path/to/my/schema.yaml -o ./path/to/my/schema.d.tspackage.jsonのscriptsに追加しておくと便利です。
{ "scripts": { "gen": "openapi-typescript openapi.json -o ./src/schema.d.ts" }}openapi-fetchとopenapi-react-queryでスキーマの型からAPIクライアントを作成する
早くも今回の山場です。先ほど生成した型定義ファイルからReact Queryのクライアントを作成します。
import createFetchClient from "openapi-fetch";import createClient from "openapi-react-query";import type { paths } from "../schema";
const fetchClient = createFetchClient<paths>({ baseUrl: import.meta.env.VITE_API_BASE_URL, // APIのベースURL (https://api.example.com)});
export const $api = createClient(fetchClient);React QueryのqueryFnにopenapi-fetchで生成した型安全なfetcherを渡しています。
React Queryはデータフェッチライブラリではなく非同期の状態管理ライブラリなので、キャッシュやローディング、エラーの状態管理のみを管理し、フェッチには関心を持ちません。この辺はSWRなどのデータフェッチライブラリと思想が大きく違いそうです。
React Queryはデータフェッチライブラリではない。非同期の状態管理ライブラリだ。
SWRに慣れている方はswr-openapiというのもあるみたいです。(日本語だとなぜか一覧に出てこず悲しい)
実際にAPIを叩いてみる
const { data: users } = $api.useQuery("get", "/users/{user_id}", { params: { path: { user_id: 0 }, }, { staleTime: 0, }});第一引数にHTTPメソッド名, 第二引数にパス、第三引数にパラメータ(パスやクエリパラメータ)、第四引数にReact Queryに渡したい引数(staleTimeなど)を渡します。 一部openapi-react-queryが自動で設定しているため指定できないものもあります(queryKey, queryFn)
例えば、/usersというエンドポイントがあった場合、これだけでデータを取得できます。
const { data: users } = $api.useQuery("get", "/users");TIPS
React Queryの使い方はこの記事が分かりやすいです。
mutationしたときにqueryがすぐに更新されない
onSuccessコールバックでクエリのinvalidationをするのが簡単です。queryClient.invalidateQueries()でクエリをinvalidateするとstaleTimeに関わらず即座にstale状態となり再取得されます。
openapi-react-queryでは$api.queryOption()を使って自動設定されたqueryKeyを取得できるようです。
import { useQueryClient } from "@tanstack/react-query";
const queryClient = useQueryClient();
const updateUser = $api.useMutation( "post", "/users/{user_id}", { params: { path: { user_id: 0 }, }, }, { onSuccess: () => queryClient.invalidateQueries({ queryKey: $api.queryOptions("get", "/users").queryKey, }), },);もしくは、setQueryData()を使って直接キャッシュを書き換えることもできます。
const mutation = $api.useMutation( "post", "/users/{user_id}", { params: { path: { user_id: 0 }, }, }, { onSuccess: (data) => { queryClient.setQueryData( $api.queryOptions("get", "/users").queryKey, data, ); }, },);Optimistic Updatesという考え方もあります。mutationが成功することを期待してUIを先に更新しておくという考え方です。この場合はonSuccessの代わりにonSettledを使うと良さそうです。