Для чего нужны guards?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Guards в TypeScript
Guards (охранники типов) — это механизм, который помогает TypeScript и разработчикам определить точный тип значения во время выполнения программы. Они критически важны для работы с динамическими данными, особенно в backend-приложениях, где данные приходят из внешних источников.
Основная проблема
В runtime TypeScript типы исчезают, остаётся обычный JavaScript:
function processData(data: unknown) {
// TypeScript не знает, что здесь число!
// Может быть строка, объект, null, всё что угодно
console.log(data.toFixed(2)); // Ошибка!
}
Guards решают эту проблему.
Type Guards
1. typeof guard — проверка примитивных типов
function printLength(value: unknown) {
if (typeof value === 'string') {
// Теперь TypeScript знает, что это строка
console.log(value.length); // ОК
} else if (typeof value === 'number') {
console.log(value.toFixed(2)); // ОК
}
}
2. instanceof guard — проверка объектов и классов
class User {
constructor(public name: string) {}
}
function handleObject(obj: unknown) {
if (obj instanceof User) {
console.log(obj.name); // ОК, TypeScript знает тип
}
if (obj instanceof Date) {
console.log(obj.getFullYear()); // ОК
}
}
3. Пользовательские type guards (type predicate)
Самые мощные guards — это функции с типом is:
interface User {
id: number;
name: string;
email: string;
}
// Это guard функция
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj &&
'email' in obj &&
typeof (obj as any).id === 'number' &&
typeof (obj as any).name === 'string' &&
typeof (obj as any).email === 'string'
);
}
const data = JSON.parse(someString);
if (isUser(data)) {
// Теперь TypeScript уверен, что это User
console.log(data.email);
} else {
console.error('Invalid user data');
}
4. in guard — проверка свойств объекта
function processPerson(obj: unknown) {
if (typeof obj === 'object' && obj !== null) {
if ('isAdmin' in obj) {
// Это администратор
console.log('Admin detected');
}
}
}
Guards в Backend контексте
Валидация API запросов
interface CreateUserDTO {
name: string;
email: string;
age: number;
}
function isCreateUserDTO(data: unknown): data is CreateUserDTO {
return (
typeof data === 'object' &&
data !== null &&
typeof (data as any).name === 'string' &&
typeof (data as any).email === 'string' &&
typeof (data as any).age === 'number' &&
(data as any).age > 0
);
}
app.post('/users', (req, res) => {
const data = req.body;
if (!isCreateUserDTO(data)) {
return res.status(400).json({ error: 'Invalid data' });
}
// Теперь мы уверены в типе data
createUser(data); // data типизирован как CreateUserDTO
});
Работа с Union типами
type Response = { status: 'success'; data: any } | { status: 'error'; message: string };
function isSuccessResponse(res: Response): res is { status: 'success'; data: any } {
return res.status === 'success';
}
const response = fetchData();
if (isSuccessResponse(response)) {
console.log(response.data);
} else {
console.error(response.message);
}
Работа с внешними API
interface ExternalAPIResponse {
userId: number;
userName: string;
}
function isExternalAPIResponse(obj: unknown): obj is ExternalAPIResponse {
return (
typeof obj === 'object' &&
obj !== null &&
typeof (obj as any).userId === 'number' &&
typeof (obj as any).userName === 'string'
);
}
async function fetchUserFromAPI(id: string) {
const response = await fetch(`https://api.example.com/user/${id}`);
const data = await response.json();
if (isExternalAPIResponse(data)) {
return data; // Безопасно!
} else {
throw new Error('Unexpected API response format');
}
}
Лучшие практики
Используй утилиты для валидации
Для сложных guards лучше использовать библиотеки:
// 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>;
const data = JSON.parse(someString);
const result = UserSchema.safeParse(data);
if (result.success) {
// result.data типизирован как User
console.log(result.data);
}
Почему guards важны
- Runtime безопасность: защита от неожиданных типов данных
- Раннее обнаружение ошибок: перехватываем проблемы до того, как они пойдут дальше
- Самодокументирующийся код: ясно видно, какие типы ожидаются
- Developer experience: IDE подсказывает корректные методы после guard
Guards — неотъемлемая часть надёжного TypeScript backend кода.