Работа с API и внешними данными
Данные из API приходят как unknown. Нужно проверить их и привести к типу.
Проблема
async function getUser(id: number) {
const res = await fetch(`/api/users/${id}`);
return res.json(); // Promise<any>
}
any теряет всю пользу типизации.
Приведение через as
Быстро, но небезопасно:
async function getUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json() as Promise<User>;
}
Если сервер вернёт другую структуру — ошибка всплывёт в рантайме.
Type guard
Правильный путь — проверить данные:
type User = {
id: number;
name: string;
email: string;
};
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
typeof (value as User).id === 'number' &&
'name' in value &&
typeof (value as User).name === 'string'
);
}
async function getUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
const data: unknown = await res.json();
if (!isUser(data)) {
throw new Error('Invalid user data');
}
return data;
}
Zod
Библиотека для валидации с выводом типов:
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>;
async function getUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return UserSchema.parse(await res.json());
}
z.infer автоматически выводит тип из схемы. Одно определение — и валидация, и тип.
Zod: примеры
const PostSchema = z.object({
id: z.number(),
title: z.string().min(1),
tags: z.array(z.string()),
published: z.boolean().optional(),
createdAt: z.string().datetime(),
});
type Post = z.infer<typeof PostSchema>;
Вложенные схемы
const AddressSchema = z.object({
city: z.string(),
zip: z.string(),
});
const UserSchema = z.object({
name: z.string(),
address: AddressSchema,
});
Union в Zod
const ShapeSchema = z.discriminatedUnion('kind', [
z.object({ kind: z.literal('circle'), radius: z.number() }),
z.object({ kind: z.literal('square'), side: z.number() }),
]);
Transform
const DateSchema = z.string().transform((s) => new Date(s));
Safe parse
const result = UserSchema.safeParse(data);
if (result.success) {
console.log(result.data); // User
} else {
console.log(result.error.issues);
}
Когда что использовать
- Одно поле —
typeof - Простой объект — type guard
- Сложные данные — Zod или аналог
- Формы — Zod + React Hook Form
Другие библиотеки
- Yup — старая, но популярная
- Valibot — лёгкая альтернатива
- ArkType — быстрая, типы как синтаксис
- io-ts — функциональный подход
Итоги
- Данные из API —
unknown - Type guards для проверки
- Zod для сложных схем
z.inferдаёт тип из схемы- Валидация и тип — из одного источника