Композиция схем в Yup строится вокруг идеи повторного использования валидаторов и их объединения в более сложные структуры без дублирования логики. Библиотека предоставляет набор примитивов, которые позволяют собирать вложенные, условные и динамические схемы в единое дерево проверки данных.
Основой служит комбинирование объектов и массивов через
object() и array(). Каждая схема описывает
самостоятельный фрагмент структуры данных, который затем включается в
более крупную модель.
import * as Yup from 'yup';
const nameSchema = Yup.object({
firstName: Yup.string().required(),
lastName: Yup.string().required()
});
const contactSchema = Yup.object({
email: Yup.string().email().required(),
phone: Yup.string()
});
Такие схемы изолированы и могут использоваться независимо, но их ценность раскрывается при объединении в составные структуры.
Метод concat позволяет последовательно объединять схемы
одного типа, расширяя их правилами из другой схемы. Это особенно полезно
при наращивании валидации без изменения исходной логики.
const baseSchema = Yup.object({
id: Yup.number().required()
});
const extendedSchema = baseSchema.concat(
Yup.object({
createdAt: Yup.date().required()
})
);
При объединении объектов поля второй схемы дополняют первую. Если ключи совпадают, поведение зависит от типа валидатора и порядка объединения, что делает композицию предсказуемой при строгой организации слоёв.
Сложные схемы часто содержат глубоко вложенные объекты. Yup
поддерживает рекурсивную композицию через object.shape, где
каждый уровень может включать собственные схемы.
const addressSchema = Yup.object({
city: Yup.string().required(),
street: Yup.string().required()
});
const userSchema = Yup.object({
name: Yup.string().required(),
address: addressSchema
});
Вложенность не ограничена уровнем глубины, что позволяет моделировать сложные доменные структуры, такие как заказы, профили или конфигурации систем.
Для массивов используется array().of(), где внутренняя
схема задаёт структуру элементов:
const orderSchema = Yup.object({
items: Yup.array().of(
Yup.object({
productId: Yup.number().required(),
quantity: Yup.number().min(1).required()
})
)
});
Одним из ключевых инструментов построения гибких схем является
when, позволяющий изменять структуру в зависимости от
значений других полей.
const schema = Yup.object({
type: Yup.string().required(),
companyName: Yup.string().when('type', {
is: 'business',
then: schema => schema.required(),
otherwise: schema => schema.notRequired()
})
});
Композиция через when превращает схему в адаптивную
систему правил, где структура данных определяется контекстом.
Условные схемы могут ссылаться на несколько полей одновременно:
const schema = Yup.object({
password: Yup.string(),
confirmPassword: Yup.string().when('password', (password, schema) =>
password ? schema.required().oneOf([Yup.ref('password')]) : schema
)
});
Схемы часто выносятся в отдельные модули и комбинируются в разных частях приложения. Такой подход снижает связность и упрощает поддержку.
export const timestampFields = {
createdAt: Yup.date().required(),
updatedAt: Yup.date().required()
};
export const baseEntitySchema = Yup.object({
id: Yup.number().required(),
...timestampFields
});
Распространённый приём — композиция через spread-оператор, где
отдельные блоки схем объединяются в единый объект перед передачей в
object().
Когда структура данных неизвестна заранее, используется
lazy, позволяющий вычислять схему во время выполнения.
const dynamicSchema = Yup.lazy(value => {
if (typeof value === 'string') {
return Yup.string().min(3);
}
if (typeof value === 'number') {
return Yup.number().min(10);
}
return Yup.mixed();
});
Этот механизм особенно важен при работе с API, где формат данных может меняться в зависимости от условий или версии ответа.
Хотя в Yup нет классического наследования, поведение схожее достигается через композицию и повторное использование базовых схем.
const baseUser = Yup.object({
username: Yup.string().required(),
role: Yup.string().required()
});
const adminUser = baseUser.concat(
Yup.object({
permissions: Yup.array().of(Yup.string()).required()
})
);
Такой подход позволяет строить иерархию схем, где каждая последующая добавляет или уточняет правила.
Дополнительное расширение возможно через функцию-генератор:
const createUserSchema = (extraFields = {}) =>
Yup.object({
username: Yup.string().required(),
...extraFields
});
const extended = createUserSchema({
email: Yup.string().email().required()
});
Массивы в Yup предоставляют отдельный уровень композиции, где вложенные схемы могут быть динамически комбинированы.
const tagSchema = Yup.object({
name: Yup.string().required()
});
const schema = Yup.object({
tags: Yup.array().of(tagSchema)
});
В более сложных случаях схема элемента массива сама может быть результатом композиции:
const baseItem = Yup.object({
id: Yup.number().required()
});
const detailedItem = baseItem.concat(
Yup.object({
description: Yup.string()
})
);
const schema = Yup.object({
items: Yup.array().of(detailedItem)
});
При проектировании сложных форм и API-валидаторов часто используется принцип слоёв:
Каждый слой строится через композицию предыдущего, формируя дерево зависимостей.
const product = Yup.object({
id: Yup.number(),
title: Yup.string()
});
const cartItem = Yup.object({
product,
quantity: Yup.number().min(1)
});
const cart = Yup.object({
items: Yup.array().of(cartItem)
});
Дополнительный уровень объединения схем достигается через
transform, позволяющий изменять данные перед валидацией и
тем самым влиять на итоговую структуру.
const schema = Yup.string().transform(value =>
value ? value.trim() : value
);
Трансформации часто используются как «скрытый слой композиции», подготавливающий данные к проверке другими схемами.
Сложные схемы выигрывают от разбиения на независимые блоки, каждый из которых описывает одну ответственность. Эти блоки затем объединяются в более крупные структуры без потери читаемости.
const authFields = {
email: Yup.string().email().required(),
password: Yup.string().min(8).required()
};
const profileFields = {
firstName: Yup.string(),
lastName: Yup.string()
};
const schema = Yup.object({
...authFields,
...profileFields
});
Такой подход делает композицию схем декларативной и управляемой на уровне структуры, а не логики.