При работе с формами и входными данными в JavaScript часто возникает необходимость не только проверять корректность значений, но и гарантировать согласованность типов на уровне приложения. Библиотека Yup предоставляет декларативный способ описания схем валидации, а при использовании с TypeScript позволяет добиться частичной или полной типобезопасности через вывод типов из схем.
Схема валидации в Yup представляет собой объект, описывающий
структуру данных и правила проверки. В TypeScript-окружении эта схема
может быть источником типов, если используется механизм вывода через
InferType.
import * as Yup from "yup";
const userSchema = Yup.object({
id: Yup.number().required(),
name: Yup.string().required(),
email: Yup.string().email().required(),
});
С точки зрения TypeScript, сама по себе схема не гарантирует типобезопасность результата. Для этого используется вывод типа:
type User = Yup.InferType<typeof userSchema>;
Теперь тип User автоматически соответствует структуре
схемы.
Механизм InferType анализирует структуру схемы и
преобразует её в соответствующий TypeScript-тип:
Yup.string() → stringYup.number() → numberYup.boolean() → booleanYup.array(Yup.string()) → string[]Yup.object({...}) → объект с соответствующими
полямиЭто позволяет синхронизировать runtime-валидацию и compile-time типы.
При создании объектных схем важно учитывать поведение ключей и необязательных полей. По умолчанию Yup допускает частичную типизацию, поэтому используется строгая конфигурация.
const productSchema = Yup.object({
id: Yup.string().required(),
price: Yup.number().required(),
description: Yup.string().optional(),
}).noUnknown(true);
Метод noUnknown(true) ограничивает добавление лишних
полей, приближая поведение схемы к строгим типам TypeScript.
Для более предсказуемого вывода типов используется явное определение структуры:
const schema = Yup.object({
title: Yup.string().required(),
count: Yup.number().required(),
} as const);
Такой подход позволяет избежать расширения типов и сохраняет буквальные значения в процессе вывода.
Типобезопасность зависит от корректного отражения необязательных
полей. В Yup это реализуется через optional() и
required().
const profileSchema = Yup.object({
username: Yup.string().required(),
bio: Yup.string().optional(),
age: Yup.number().notRequired(),
});
В TypeScript это приводит к следующей структуре:
username: stringbio?: stringage?: numberПри работе со сложными структурами важную роль играет композиция схем.
const addressSchema = Yup.object({
city: Yup.string().required(),
zip: Yup.string().required(),
});
const userSchema = Yup.object({
name: Yup.string().required(),
address: addressSchema.required(),
});
Типизация автоматически становится вложенной:
type User = {
name: string;
address: {
city: string;
zip: string;
};
};
Схемы массивов позволяют описывать тип элементов и гарантировать однородность структуры.
const tagsSchema = Yup.array(Yup.string().required());
Вывод типа:
string[]
Для объектов:
const usersSchema = Yup.array(
Yup.object({
id: Yup.number().required(),
name: Yup.string().required(),
})
);
Результат:
Array<{ id: number; name: string }>
Хотя Yup не использует generics напрямую в API, типобезопасность достигается через обобщённое описание схем и повторное использование типов.
type BaseEntity = {
id: number;
};
const baseSchema = Yup.object({
id: Yup.number().required(),
});
Дальнейшие схемы могут расширять базовую структуру:
const postSchema = baseSchema.shape({
title: Yup.string().required(),
});
Для сложных моделей применяется композиция схем через
concat.
const a = Yup.object({
a: Yup.string().required(),
});
const b = Yup.object({
b: Yup.number().required(),
});
const combined = a.concat(b);
Тип результирующей схемы:
{ a: string; b: number }
Тип mixed используется для произвольных значений, но при
этом теряется часть типобезопасности. Его можно уточнять через кастомные
проверки.
const customSchema = Yup.mixed()
.test("is-date", "Invalid date", (value): value is Date => value instanceof Date);
Такой подход позволяет вручную восстановить контроль типов.
Схемы могут изменять входные данные, что влияет на итоговый тип.
const numberFromString = Yup.string().transform((value, originalValue) => {
return Number(originalValue);
});
Здесь важно учитывать, что итоговый тип становится
number, несмотря на исходный string.
После валидации результат приводится к типу, выведенному из схемы:
const data: User = await userSchema.validate(input);
Если схема и типы синхронизированы, дальнейшая работа с
data становится безопасной без дополнительных проверок.
При модификации схем важно сохранять соответствие типов. Расширение
через shape позволяет добавлять новые поля без разрушения
структуры:
const extendedUser = userSchema.shape({
role: Yup.string().required(),
});
Результирующий тип автоматически включает новое поле.
Несмотря на возможности TypeScript-интеграции, типобезопасность в Yup имеет ограничения:
Поэтому типы рассматриваются как производные от схемы, а не как первичный источник истины.
В сложных приложениях схема становится отражением доменной модели. Поддержание синхронизации достигается через единый источник описания:
const orderSchema = Yup.object({
id: Yup.string().required(),
total: Yup.number().required(),
status: Yup.mixed<"pending" | "paid" | "cancelled">().required(),
});
Тип выводится напрямую:
type Order = Yup.InferType<typeof orderSchema>;
Такой подход уменьшает дублирование и снижает риск рассинхронизации логики и типов.