Хотя мой текущий стек основан на фантастическом стеке T3 Stack, использующем tRPC, я также являюсь ведущим frontend-разработчиком приложения на JavaScript React, созданного с помощью Create React App на основе Rails API. Перенося это приложение на TypeScript, я обнаружил, что жажду автоматической безопасности типов, которую обеспечивает tRPC.
К счастью, существует относительно простое решение проблемы взаимодействия с нетипизированными API в React на базе TypeScript.
Строительные блоки
Первой частью этой головоломки является невероятный React Query. Если вы еще не используете React Query в своем проекте, вам следует немедленно сделать шаг назад и спросить себя, почему. Выборка данных уже давно является спорной темой в React, а после появления хуков многие разработчики (в том числе и я) совершили ошибку, неправильно используя useEffect
или создавая собственные библиотеки для выборки данных. Как и в случае с собственной системой аутентификации, это может вас подкосить.
Вторая часть — это Zod. Хотя существует множество библиотек валидации, Zod на данный момент является лидером, и вы должны использовать его, если только у вас нет абсолютной причины использовать что-то другое. Валидация Zod и способность легко выводить типы TypeScript станут ключом к типизации нашего нетипизированного API.
Собираем все вместе
Решение на удивление лаконично: Написать валидатор Zod для конечной точки API, использовать его для разбора ответа API и получить типизированный результат в React Query. Давайте рассмотрим пример этого на примере конечной точки API с информацией о пользователе:
// Schema for what the API endpoint should be returning
const UserSchema = z.object({
id: z.number(),
email: z.string().email(),
displayName: z.string().min(1),
role: z.enum(['USER', 'ADMIN']),
});
// Optionally, if you want to export this type to pass around
// elsewhere, you can export it:
export type User = z.infer<typeof UserSchema>;
// Query the endpoint and parse the response with Zod schema,
// which will type the response for us
const query = useQuery(["user"], async () => {
const response = await (await fetch('/me')).json();
return UserSchema.parse(response);
});
query.data // will be typed
Вот и все! Результирующий query.data
будет типизирован либо как схема, либо как undefined
(поскольку запросы могут быть неудачными). Если ответ от API не пройдет валидацию, вы получите ошибку, с которой можно разобраться в процессе разработки или даже поймать в продакшене с помощью инструмента регистрации исключений, который вы используете.
Хотя это решение не такое автоматическое, как tRPC, и требует написания некоторого количества шаблонов, оно хорошо масштабируется и делает работу с нетипизированными конечными точками API менее болезненной в TypeScript.