投稿日
React QueryとOpenAPI定義ファイルからのコード自動生成ツールOrvalを使ったWebアプリケーション開発
React Queryとは
- 通信結果のキャッシュをアプリケーション単位で管理することで無駄な通信や画面間のデータ受け渡しによる複雑化を避けることができる
- 非同期通信の状態管理を楽にできる
Orvalとは
Orval利用時のポイント
設定
backend: {
output: {
mode: 'tags-split',
clean: true,
mode
clean
自動生成されたコードの利用方法
'/todos/{todoId}':
parameters:
- name: todoId
in: path
description: Todo ID
required: true
schema:
type: number
get:
summary: Get todo
description: Get todo
tags: []
operationId: get-todo
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Todo'
~中略~
put:
summary: Update todo
description: Update todo
tags: []
operationId: put-todo
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/TodoRegistration'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Todo'
/**
* Get todo
* @summary Get todo
*/
export const getTodo = (todoId: number) => {
return sandboxCustomInstance<Todo>({url: `/todos/${todoId}`, method: 'get'});
};
export const getGetTodoQueryKey = (todoId: number) => [`/todos/${todoId}`];
export const useGetTodo = <TData = AsyncReturnType<typeof getTodo>, TError = ErrorType<NotFoundResponse>>(
todoId: number,
options?: {query?: UseQueryOptions<AsyncReturnType<typeof getTodo>, TError, TData>},
): UseQueryResult<TData, TError> & {queryKey: QueryKey} => {
const {query: queryOptions} = options || {};
const queryKey = queryOptions?.queryKey ?? getGetTodoQueryKey(todoId);
const queryFn: QueryFunction<AsyncReturnType<typeof getTodo>> = () => getTodo(todoId);
const query = useQuery<AsyncReturnType<typeof getTodo>, TError, TData>(queryKey, queryFn, {
enabled: !!todoId,
...queryOptions,
});
return {
queryKey,
...query,
};
};
/**
* Update todo
* @summary Update todo
*/
export const putTodo = (todoId: number, todoRegistration: TodoRegistration) => {
return sandboxCustomInstance<Todo>({url: `/todos/${todoId}`, method: 'put', data: todoRegistration});
};
export const usePutTodo = <TError = ErrorType<BadRequestResponse | NotFoundResponse>, TContext = unknown>(options?: {
mutation?: UseMutationOptions<
AsyncReturnType<typeof putTodo>,
TError,
{todoId: number; data: TodoRegistration},
TContext
>;
}) => {
const {mutation: mutationOptions} = options || {};
const mutationFn: MutationFunction<
AsyncReturnType<typeof putTodo>,
{todoId: number; data: TodoRegistration}
> = props => {
const {todoId, data} = props || {};
return putTodo(todoId, data);
};
return useMutation<AsyncReturnType<typeof putTodo>, TError, {todoId: number; data: TodoRegistration}, TContext>(
mutationFn,
mutationOptions,
);
};
サンプルアプリケーションの「自動生成されたコードの利用」に記載されているように、自動生成されたカスタムフックを直接利用せずに途中にService層を挟むことで必要に応じてカスタマイズ可能な余地を設けることができます。
カスタマイズの一例として、サンプルアプリケーションの「データ更新時のキャッシュの扱いについて」に記載があるように、データの更新に成功した際にReact Queryがキャッシュした古いキャッシュデータの破棄などがあります。キャッシュデータの破棄をこのサービス層でおこなうことで、自動生成コードは持たない処理を追加できるかつ、キャッシュデータの破棄に関する処理を集約できます。※3
以下のコード例では自動生成されたQuery Keysを取得する処理を利用してクエリ成功時にキャッシュを破棄しています。
【サービス層でキャッシュデータの破棄をおこなうコード例】
const usePutTodo = () => {
const queryClient = useQueryClient();
// Orvalによって自動生成されたカスタムフック(usePutTodoApi)に処理を追加している
return usePutTodoApi({
mutation: {
onSuccess: (_, variables) => resetQueries(queryClient, variables.todoId),
},
});
};
~中略~
const resetQueries = async (queryClient: QueryClient, todoId?: number) => {
~中略~
if (todoId) {
// Query Keysを取得する処理を利用してクエリ成功時にキャッシュを破棄している
await queryClient.resetQueries(getGetTodoQueryKey(todoId));
}
};
サービス層で作成したカスタムフックはそれぞれの画面で利用可能です。
以下のコード例は取得した情報を表示・編集する画面の一部でtodoIdをもとに情報を取得したり入力値(title, description)をバックエンドアプリケーションに送信して情報を更新しています。
【画面でサービス層のカスタムフックを利用するコード例】
//todoIdをもとに情報の取得している
const todoQuery = useGetTodo(todoId);
const todo = todoQuery.data?.data;
const putTodo = usePutTodo();
~中略~
const onSave = useCallback(async () => {
~中略~
const data = {title, description};
//入力値(title, description)をバックエンドアプリケーションに送信して情報の更新している
await putTodo.mutateAsync({todoId, data});
setIsEdit(false);
OpenAPI定義ファイルの管理ルール
Orvalを利用するにあたって、開発チーム内でOpenAPI定義ファイルをどのように管理していたかも紹介します。
私達の開発チームではOpenAPI定義ファイルをWebアプリケーションのソースコードと同じリポジトリに入れて管理していました。また、OpenAPI定義ファイルに変更がある場合はかならずOrvalによるコード自動生成とコミットをセットにし、OpenAPI定義ファイルの状態と自動生成されたコードの状態が常に一致する状態にすることをルールとすることでコードの状態を把握しやすくなるのでお勧めです。
最後に
冒頭にも書きましたが、React Queryを利用した通信状態の管理とOrvalを利用したコード自動生成の組み合わせは効果的で、開発コストの削減にも役立ちました。また、サンプルアプリケーションは、Reactを使ったWebアプリケーション開発にも親和性があり、周辺ライブラリを使った開発技法も十分に活かすことができます。今後もFintanのReact Nativeに関する記事に注目して、Webアプリケーション開発に活かせるものがあれば紹介します。
※1: 参照元のサンプルアプリケーションは改善・更新されていくため、本記事作成時点でのリンクを貼っています。参照する際は最新版もご確認ください。
※2: 記事公開時点ではReact Queryはv4でTanStack Queryと名前を変えましたが、本記事で取り上げているアプリ開発時はv3が最新だったため、記事内では一貫してReact Queryと表記しています。
※3: React Queryのキャッシュについてより理解を深める場合はサンプルアプリケーションの「React Queryの仕組み」を一度読むことをお勧めします。