Гайд по автоматической генерации хуков для RTK Query (OpenAPI v3)
OpenAPI — это стандарт для описания API, который позволяет разработчикам создавать документацию, тестировать и даже генерировать код на основе схемы (единого источника истины для бэкенда, аналитиков и фронтенда). С его помощью можно сильно упростить разработку, например, автоматически создавать хуки для RTK Query. В этом гайде я покажу, как настроить этот процесс и избавиться от рутинного ручного кодинга.
Используйте мой предыдущий туториал (тык сюда), чтобы настроить Redux Toolkit в проекте. Структура папок, импорты и нейминги, которые я буду использовать далее, будут соответствовать этому туториалу.
Не рекомендую называть файлы абстрактно и сохранять в общую папку (например, src/services/api.ts), лучше сохранить их в деректорию, указывающую на конкретное назначение API (например, src/services/petStoreApi/apiBase.ts). Так мы облегчим масштабируемость приложения, ведь нам затем может понадобиться использовать одновременно несколько API с похожими ручками.
📄 src/services/petStoreApi/apiBase.ts
Для туториала я использую официальный пример OpenAPI от Swagger - API Магазина Питомцев
Добавляем reducer из слайса нашего api, а также добавляем middleware, который активирует кэширование, инвалидацию кэша, поллинг и другие фичи RTK-Query:
📄 src/store/index.ts
Обратите внимание на расширение файла - .cjs. Это наиболее универсальный вариант для конфигурации, который позволяет не использовать сторонние зависимости, такие как ts-node. Я честно пытался запустить конфигурацию в формате .ts, но удалось мне это только после 15 минут фиксов ошибок запуска, поэтому я решил, что проще сразу использовать .cjs. Да, так мы лишимся подсказок и автодополнения в этом файле, но, справедливости ради, не так уж много в нем параметров, а возможные ошибки мы сможем отследить при запуске.
📄 src/services/petStoreApi/openapi-codegen-config.cjs
Если в вашей OpenAPI спецификации используются тэги, вы можете добавить в конфигурации
tag: true
. Тогда во всех сгенерированных эндпоинтах появятся определения
providesTags для query, и invalidatesTags для mutation соответственно. Но заметьте, что
сгенерируются только простые тэги без сложного поведения и id, что может привести к
ненужным
инвалидациям кэша и лишним запросам к серверу в мутациях.
Мне кажется, лучше вручную указывать параметры providesTags/invalidatesTags с помощью enhanceEndpoints поверх сгенерированного api, чтобы случайно не заспамить бэкенд постоянными запросами, так как если на одной странице будет много компонентов зависимых от общего тэга, вызов одной мутации будет отправлять заново все связанные с ее тэгом запросы, даже те которые мы не ожидаем. Как использовать enhanceEndpoints я покажу далее в гайде.
Также вы можете сгенерировать несколько выходных файлов используя параметры filterEndpoints и outputFiles. Полезно, когда ваш исходный API имеет широкую специфику, и вы хотите явно разделить сущности. Например:
Аналогом npx
в pnpm является команда pnmp dlx
- используем
ее и плагин @rtk-query/codegen-openapi
для генерации кода:
(Опционально) Можете добавить эту команду как скрипт в package.json, чтобы быстрее ее вызывать и использовать в CI/CD пайплайне
Таким образом мы получим файл api.generated.ts с хуками, которые
мы сможем использовать в наших компонентах. ВАЖНО: Ни в коем случае не пытайтесь вручную
изменять сгенерированный файл!
Ведь если api еще в разработке, при каждой
повторной кодогенерации, все
изменения будут удаляться.
Создадим компонент, который будет получать информацию о питомце по id. Импортируем нужный нам сгенерированный хук, благодаря React Query он способен самостоятельно следить за статусом запроса, кэширует данные и обновляет компонент при их изменении:
📄 src/components/petInfo/petInfo.tsx
Попробуйте дополнить этот компонент самостоятельно, например, добавьте в него поле input для ввода id питомца, и проследите за отправкой запросов через вкладку Network в devTools - вы увидите как RTK Query их кэширует.
Важно помнить, что RTK Query спроектирован так, чтобы обеспечить нам максимальную декларативность при использовании в связке с React. Не нужно пытаться использовать createAsyncThunk или напрямую обращаться к API, ведь RTK Query самостоятельно следит за статусом запроса, кэширует данные и обновляет компонент при их изменении. Если вы хотите имплементировать что-то сложное, чтобы обеспечить общую консистентность, лучшим решением будет создать новый хук, который будет использовать внутри себя сгенерированные хуки.
Предположим, что мы хотим создать нового питомца и после этого обновить информацию получаемую по id. У нас уже есть компонент counter, который мы для примера используем как генератор id питомца. Обновим в соответствии с этим компонент PetInfo:
📄 src/components/petInfo/petInfo.tsx
Теперь создадим новый компонент, который будет отправлять запрос на создание питомца и обновлять информацию о нем:
📄 src/components/petInfo/addPet.tsx
Но мы увидим, что при добавлении питомца, информация о нем по id не обновляется автоматически, а только после обновления страницы. Для того чтобы исправить это поведение, как мы обсуждали ранее, нужно добавить к эндпоинтам определения providesTags и invalidatesTags. Это можно сделать двумя способами.
Способ 1
: Если в OpenAPI схеме прописаны тэги, то можно просто добавить
свойство
tags: true
в openapi-codegen-config.cjs и повторно сгенерировать файл
api.
Способ 2
: вручную добавить определения providesTags и invalidatesTags с
помощью
enhanceEndpoints. Для этого создадим новый файл apiEnhanced:
📄 src/services/petStoreApi/apiEnhanced.ts
Теперь вместо импортирования из сгенерированного api, импортируем хуки из apiEnhanced в наших компонентах:
В результате сразу после создания нового питомца с помощью useAddPetMutation кэш с тэгом Pet будет инвалидироваться, что приведет к повторному вызову запроса из useGetPetByIdQuery и автоматическому обновлению нужных нам данных.
Если у вас есть сгенерированный эндпоинт, который принимает несколько аргументов, вы можете создать кастомный хук, где часть данных будет уже предустановлена.
Создадим кастомный хук useFindAvailablePetsQuery, который будет возвращать список питомцев со статусом "available". За основу возьмем хук useFindPetsByStatusQuery, в котором нужно явно указать статус, чтобы сделать запрос.
📄 src/services/petStoreApi/apiEnhanced.ts
Теперь вместо импортирования из сгенерированного api, импортируем хук из apiEnhanced в наших компонентах:
📄 src/components/petInfo/availablePets.tsx
Если вам нужно модифицировать результат запроса, например, добавить новое поле или отфильтровать данные, вы можете сделать это прямо в хуке. Для примера создадим хук useFindAvailableSlavicPetsQuery, который будет возвращать список питомцев с именами на кириллице и добавит новое поле в получаемые объекты.
📄 src/services/petStoreApi/apiEnhanced.ts
Если вам нужно отправить несколько запросов параллельно и обработать их примерно также как это делает Promise.All(), то достаточно просто обработать ошибки прямо в компоненте.
📄 src/components/petInfo/parallelRequests.tsx
Попробуйте поперезагружать страницу, пока один из запросов не провалится, вы увидите, что ни один из результатов не отобразился.
Ну и напоследок самый легкий кейс - чтобы отправлять запрос циклично, просто добавьте свойство pollingInterval в используемый хук, указав интервал в миллисекундах:
📄 src/components/petInfo/petInfo.tsx
Таким образом, мы рассмотрели базовые кейсы использования RTK Query с OpenAPI и сгенерированными хуками. Надеюсь, что этот гайд поможет вам быстрее начать работу с RTK Query и упростит взаимодействие с API в ваших проектах.