Создавая приложение сложнее ToDo-листа, чаще всего нам требуется взаимодействовать с какими-то данными, хранящимися на сервере. Это могут быть как прогнозы погоды, обрабатываемые сторонним API, так и данные наших клиентов, будь то их логин и пароль или список покупок в магазине. Работая с SPA (Single Page Application) приложением, нам нужно эти самые данные получать, модифицировать и отправлять со стороны клиента. Следовательно, нужно иметь какую-то прослойку, отвечающую за взаимодействие с сервером. В этой статье рассмотрим использование API-клиента с библиотекой React, хотя ей можно смело пользоваться на том же Vue, Svelte и так далее.
Почему не прописать все запросы в компонентах, где они используются?
Все просто: если у вас поменяется интерфейс API, с которым вы работаете, вам придется пройтись по всему коду и найти все точки изменений, которые это затронуло. Можно попробовать вынести эту логику в React-хуки, раз уж сейчас речь о нем, но это решение не получится использовать в других проектах c другими фреймворками.
Реализация на Typescript
Для начала вынесем домены, где находятся API, в своего рода конфиг, работающий с .env
-файлом:
Затем напишем сам абстрактный клиент, не привязанный к данному домену. Для его работы потребуются библиотеки axios и axios-extensions.
Код клиента:
В клиенте используются пользовательские типы, такие как Headers
, который, по сути, является просто словарем [key: string]: string, и различные ошибки, которые наследуют глобальный класс Error
(Unauthorized, Forbidden, HttpError), чтобы в дальнейшем было проще понять, что послужило их причиной.
У класса всего три публичных метода, которые при каждом использовании генерируют axios-клиент. Этот клиент может работать как с публичными эндпоинтами API, так и с защищенными, путем добавления заголовка с Bearer-токеном. Как клиент получает этот самый токен, будет рассмотрено позже. Как get-, так и post-методы используют необязательный параметр abortSignal
, который позволяет прервать отправку запроса в зависимости от действий пользователя.
В случае с отправкой каких-либо файлов на сервер клиент использует метод uploadFile()
, отправляя на сервер запрос с заголовком Content-Type: multipart/form-data.
Для инкапсуляции логики создания этих клиентов, напишем фабрику.
Код фабрики:
Ничего сложного она не делает: просто создает либо обычный клиент, либо авторизованный, передавая в конструктор токен.
Конкретная реализация
Теперь нам нужно адаптировать этот абстрактный клиент под какой-то конкретный эндпоинт. Например, создадим менеджер, получающий с сервера последнее состояние профиля пользователя:
В данном примере нам не важна модель, которую мы используем для профиля. Будем просто считать, что она совместима с передаваемым с сервера значением.
Сам класс менеджера использует композицию и хранит в своем состоянии объект клиента, чтобы переадресовывать все API-запросы к нему, а если надо, он сможет добавить какую-то свою логику к полученному значению (провести валидацию, создать свой эндпоинт и так далее).
Чаще всего, API группируют доменную логику, добавляя к своим эндпоинтам определенный префикс. Также бывают случаи миграции API с одной версии на более новую. Чтобы все это предусмотреть, создадим фабрику для этого конкретного менеджера.
Код фабрики:
При создании этой фабрики, в конструктор передается URL домена и заголовки для запроса. Затем эти параметры передаются в конструктор фабрики API клиентов, дописывая после переданного URL версию API и тот самый префикс, обозначающий часть доменной логики. При создании менеджера профилей пользователей, требуется авторизация, так что в метод передается токен, на основе которого создается клиент с заголовком авторизации.
Dependency injection
Теперь осталось только написать функцию, которая будет отвечать за предоставление рабочего менеджера профилей в любой части кода, будь то React-компонент или независимый Typescript-класс. Выглядеть она будет примерно так:
Вначале внутри создается фабрика этих самых менеджеров, в которую передается домен сервера и базовые заголовки, которые выглядят так:
При желании можно добавить на уровне функции создания менеджера любые свои заголовки.
Способ получения API-токена и работы функции getAuthToken()
я не буду рассматривать в этой статье, потому что эта тема заслуживает отдельной публикации.
Использование в компонентах.
Пример работы менеджера профилей представлен ниже:
При запуске функции в хуке useEffect
, асинхронно создается менеджер профилей, который затем запрашивает с сервера текущее состояние профиля пользователей. В данном примере мы просто записываем полученное состояние в хранилище Redux, чтобы затем работать с этим профилем, не перезапрашивая каждый раз его с сервера. В случае ошибки работы клиента, запускается функция handleError()
, которая в зависимости от рода ошибки, о чем я говорил ранее, выполняет те или иные действия.
Итоги
Данная реализация независима от фреймворка, с которым вы работаете, ее можно использовать даже на нативном JS (TS). В ней можно еще много чего доработать, например добавить паттерн «Строитель» для создания API-клиента и передачи в него параметров, abortSignal-ов и прочего, или сделать вариативную систему аутентификации через JWT-токен. Все на ваше усмотрение). В следующей статье расскажу вам про способ получения и работу с API-токенами на клиенте.
Комментарии