Библиотека Yup предоставляет декларативный способ описания схем валидации данных, где одним из ключевых механизмов выступают вложенные схемы. Они позволяют описывать сложные структуры объектов, включающие уровни вложенности, массивы объектов, условную валидацию и комбинирование различных типов данных.
В основе большинства сложных схем лежит object(). Этот
тип схемы задаёт структуру объекта и определяет правила валидации для
каждого его поля отдельно.
import * as Yup from 'yup';
const schema = Yup.object({
name: Yup.string().required(),
age: Yup.number().min(18),
address: Yup.object({
city: Yup.string().required(),
zip: Yup.string().length(6)
})
});
В данном случае поле address само является объектом,
внутри которого применяется отдельная схема. Это и есть базовый механизм
вложенных схем.
Особенность подхода заключается в том, что каждая вложенная структура не наследует правила родителя, а описывается независимо, но внутри общей иерархии данных.
При работе с вложенными схемами Yup формирует путь к каждому значению. Этот путь используется для определения источника ошибки.
Пример структуры:
const schema = Yup.object({
user: Yup.object({
profile: Yup.object({
email: Yup.string().email().required()
})
})
});
Если значение email невалидно, ошибка будет ассоциирована с путём:
user.profile.email
Это поведение критически важно при интеграции с формами, где требуется точечное отображение ошибок.
Одним из наиболее распространённых случаев вложенности является
использование массивов объектов. Для этого применяется
array().of().
const schema = Yup.object({
users: Yup.array().of(
Yup.object({
id: Yup.number().required(),
name: Yup.string().required()
})
)
});
Каждый элемент массива проходит собственную валидацию по указанной схеме.
Дополнительно возможно ограничение длины массива:
Yup.array()
.of(Yup.string())
.min(1)
.max(10)
При увеличении сложности структуры возрастает необходимость композиции схем. Yup позволяет выносить повторяющиеся части в отдельные схемы и переиспользовать их.
const addressSchema = Yup.object({
city: Yup.string().required(),
street: Yup.string().required()
});
const userSchema = Yup.object({
name: Yup.string().required(),
address: addressSchema
});
Такой подход снижает дублирование и повышает читаемость.
Сложные структуры часто включают комбинацию массивов и объектов на нескольких уровнях.
const schema = Yup.object({
departments: Yup.array().of(
Yup.object({
title: Yup.string().required(),
employees: Yup.array().of(
Yup.object({
name: Yup.string().required(),
position: Yup.string()
})
)
})
)
});
Здесь формируется иерархия:
departments
employees
Каждый уровень имеет собственную схему валидации, независимую от других уровней.
Для управления поведением вложенных схем используется
when, позволяющий изменять правила в зависимости от
значения других полей.
const schema = Yup.object({
hasAddress: Yup.boolean(),
address: Yup.object().when('hasAddress', {
is: true,
then: Yup.object({
city: Yup.string().required(),
street: Yup.string().required()
}),
otherwise: Yup.object().nullable()
})
});
Такой подход позволяет динамически включать или отключать вложенную валидацию.
Когда структура данных заранее неизвестна, применяется
lazy, позволяющий создавать схему на основе входного
значения.
const schema = Yup.lazy(value => {
if (Array.isArray(value)) {
return Yup.array().of(Yup.string());
}
return Yup.object({
type: Yup.string(),
value: Yup.mixed()
});
});
Вложенные схемы в данном случае формируются во время выполнения, что особенно полезно при работе с гибкими структурами API.
Yup возвращает ошибки в виде структурированного объекта, где каждая ошибка привязана к конкретному пути.
Пример результата:
{
"users[0].name": "Name is required",
"users[1].age": "Must be greater than 18"
}
Такая структура позволяет точно сопоставить ошибку с элементом интерфейса.
Вложенные схемы можно расширять с помощью concat,
добавляя новые правила без переписывания всей структуры.
const baseSchema = Yup.object({
name: Yup.string().required()
});
const extendedSchema = baseSchema.concat(
Yup.object({
age: Yup.number().required()
})
);
Этот механизм часто используется при построении модульных схем.
Для максимально гибких структур применяется mixed,
позволяющий комбинировать различные типы внутри вложенных схем.
const schema = Yup.object({
config: Yup.mixed().test(
'is-valid-config',
'Invalid config',
value => typeof value === 'object'
)
});
Хотя такой подход снижает строгость типизации, он необходим при работе с динамическими данными.
В некоторых случаях структура данных может быть рекурсивной, например дерево:
const nodeSchema = Yup.object({
name: Yup.string().required(),
children: Yup.array().of(Yup.lazy(() => nodeSchema))
});
Рекурсивные схемы позволяют описывать произвольную глубину вложенности, что важно при работе с деревьями категорий, меню или графами.
Глубина вложенности напрямую влияет на стоимость валидации. Каждое дополнительное вложение добавляет уровень обхода структуры данных. При работе с большими объектами рекомендуется:
lazy только при необходимостиtest в глубоко вложенных
узлахТакие меры позволяют сохранять предсказуемую производительность даже при сложных структурах данных