Типи TypeScript існують лише під час компіляції. Після збирання від них не лишається нічого - браузер виконує звичайний JavaScript. Тому TypeScript перевіряє ваш код, але не дані, що приходять ззовні.
type User = { id: number; name: string; email: string };
const response = await fetch('/api/user');
const user: User = await response.json(); // response.json() повертає any
user.email.toLowerCase(); // компілюється, а якщо API повернув { data: {...} } - падає
Анотація : User - це обіцянка розробника, а не перевірка. Якщо бекенд змінив формат, перейменував поле чи повернув null, TypeScript про це не дізнається - помилка вилізе під час виконання, часто далеко від місця отримання даних.
Звідки беруться «неперевірені» дані:
- відповіді API (
response.json()має типPromise<any>); JSON.parse(тежany);localStorage, параметри URL,postMessage, WebSocket;- змінні оточення, значення полів форм.
Як захиститися - перевірка під час виконання на межі системи:
import * as z from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.email(),
});
type User = z.infer<typeof UserSchema>; // тип виводиться зі схеми
const user = UserSchema.parse(await response.json()); // кине помилку, якщо дані не такі
Схема і тип - одне джерело правди: змінили схему - змінився тип.
Правила:
- дані ззовні мають тип
unknown, доки не перевірені - а неany; - перевіряти один раз на межі (в API-клієнті), а далі код працює з надійно типізованими даними;
- помилка перевірки має бути помітною: зрозуміле повідомлення, лог у моніторинг - так розбіжність між бекендом і фронтендом виявляють одразу.
Альтернативи Zod: Valibot (менший розмір у збірці), ArkType, TypeBox. Ідея однакова - схема під час виконання, з якої виводиться тип.