Разработчики клиентских приложений ежедневно работают с API. Хорошей практикой является стандартизация ответов API в зависимости от успешности операций или бизнес-логики. Как правило, ответ включает в себя стандартные поля, такие как статус, ошибка и т. д.
С помощью этих стандартных полей разработчики могут реагировать на статус ответов и строить дальнейшее взаимодействие пользователя с приложением. Если запрос прошел успешно, форма должна быть закрыта, а на экран выведено сообщение об успехе. Однако в случае неправильного формата данных в форме должны отображаться ошибки валидации.
В связи с этим возникает вопрос о том, как удобно, быстро и гибко описать типы ответов в проекте.
Проблема, с которой я столкнулся
Иногда типы ответов в проекте описываются с помощью одного типа с большим количеством необязательных параметров. В большинстве случаев этого может быть достаточно, и TypeScript будет предлагать эти параметры при написании кода, но потребуются дополнительные проверки на наличие этих параметров. Вот пример такого типа:
Единственное преимущество этого подхода - его простота. Мы можем добавить тип ApiData к любому типу ответа, и этого будет достаточно.
Однако я считаю, что это единственное преимущество перевешивается существенным недостатком. Недостатком такого подхода является отсутствие прозрачности. Конечно это очень спорный момент, иногда такого решения вполне достаточно. И его недостатки можно покрыть написав пару кастомных хуков.
Кроме того, добавляя такой тип к типам ответа, вы никогда не знаете точно, каким будет ответ на конкретный запрос. Представьте, что для POST-запроса вы можете получить от API ограниченное количество сценариев ответа.
Эти сценарии могут быть следующими
- успешная операция со status: 'ok' и некоторые данные
- ошибка валидации со status: 'form_errors' и errors: [{}, {}], и это все
Это означает, что в этом случае у вас никогда не будет статуса: 'redirect' в качестве возможного сценария ответа. Кроме того, зачем вам нужен параметр errors для ответа на GET-запросы?
Оказывается, мы не можем понять, какие именно варианты ответа у нас есть, просто посмотрев на тип ответа. Чтобы понять все возможные варианты ответа, нужно открыть код функции, которая делает запрос и обрабатывает ответ.
Utility типы для типов ответов
Описанные выше недостатки можно устранить с помощью пользовательских типов утилит. Для каждого сценария существует отдельный тип: успешная операция, ошибка сервера, ошибка проверки или принудительное перенаправление.
Эти типы можно использовать по отдельности или комбинировать, чтобы отразить все возможные варианты ответа для конкретного ответа. Каждый тип будет иметь дженерик, позволяющий передавать тип данных, соответствующий этому ответу.
Кроме того, я создал общий тип ApiRespinse, который включает в себя несколько типов утилит. Это позволит сэкономить время на добавление всех сценариев для каждого POST-запроса.
Ниже приведены примеры использования этих типов утилит для различных сценариев:
Практическое различие
Ниже приведен пример типов для профиля пользователя и ответа, возвращаемого функцией обновления профиля пользователя.
Здесь показано, как TypeScript анализирует этот код:
На изображении видно, что некоторые ожидаемые значения для стандартных ответов, такие как error, errors или url, выделены TypeScript. Это происходит потому, что линтер считает, что эти значения могут быть неопределенными. Это легко решается с помощью дополнительной проверки вместе со статусом, но это уже показывает проблему с таким подходом.
Также обратите внимание, что в строке с console.log(data.user.id) значение user не выделено как потенциально undefined. Так будет, если мы получим любой тип ответа, кроме успешного.
Используя такие типы утилит, как ApiResponse и другие, мы не столкнемся с подобными проблемами.
Здесь показано, как TypeScript анализирует этот код:
В этом случае все работает, как и ожидалось:
- TypeScript понимает, что для соответствующих статусов будут соответствующие стандартные поля.
- Это указывает на то, что userзначение может быть undefined во всех типах ответов, кроме успешного. Однако после проверки успешности ответа это значение не выделяется и определяется.
Заключение
После внедрения этих типов утилит в проект значительно улучшился опыт разработчиков. Теперь типы полностью соответствуют возможным сценариям ответа, которые может предоставить API.
Это также поможет избежать потенциальных ошибок, когда могут быть использованы значения, недоступные в определенных типах ответов, как в примере со значением пользователя.
Кроме того, чтобы понять реальные типы ответов, не нужно смотреть на реализацию обработки ответа в коде. Вы можете сразу увидеть полную картину.
Ссылки на наши ресурсы – ниже: