Композиция в Yup строится вокруг идеи повторного использования схем и их объединения в более сложные структуры без дублирования логики валидации. Библиотека предоставляет несколько механизмов, которые позволяют собирать валидаторы как из строительных блоков, формируя единый контракт данных.
Валидационные схемы в Yup являются иммутабельными объектами. Это означает, что любые операции расширения или модификации возвращают новую схему, не изменяя исходную. Такой подход позволяет безопасно комбинировать правила.
Основные инструменты композиции:
concat — объединение схем одного типаobject.shape — расширение структуры объектаmixed и кастомные типы — базовые расширенияwhen — условная композицияlazy — отложенное определение схемaddMethod — расширение прототипа (аналог миксинов)Метод concat применяется для объединения двух схем
одинакового типа. Он часто используется для наращивания базовой
логики.
import * as Yup from 'yup';
const baseString = Yup.string()
.min(3, 'Минимум 3 символа');
const withMaxLength = Yup.string()
.max(10, 'Максимум 10 символов');
const composed = baseString.concat(withMaxLength);
Результирующая схема включает оба ограничения: минимальную и максимальную длину.
Важно учитывать, что concat работает только для
совместимых типов. Попытка объединить string и
number приведёт к ошибке.
Для объектных схем основным механизмом расширения выступает
shape. Он позволяет добавлять новые поля поверх базовой
структуры.
const baseUserSchema = Yup.object({
id: Yup.number().required(),
email: Yup.string().email().required()
});
const extendedUserSchema = baseUserSchema.shape({
role: Yup.string().oneOf(['admin', 'user']),
createdAt: Yup.date()
});
При использовании shape сохраняются все исходные поля, а
новые добавляются в результирующую схему.
Распространённый подход — выделение базовых сущностей:
Каждый следующий слой строится поверх предыдущего.
Механизм addMethod позволяет расширять встроенные типы
Yup собственными методами, что концептуально близко к миксинам.
Yup.addMethod(Yup.string, 'startsWithUppercase', function (message) {
return this.test('starts-with-uppercase', message, function (value) {
if (!value) return true;
return /^[A-Z]/.test(value);
});
});
Использование:
const schema = Yup.string()
.startsWithUppercase('Должно начинаться с заглавной буквы');
Такой подход позволяет формировать библиотеку переиспользуемых валидаторов, которые выглядят как встроенные методы.
Типичные сценарии миксинов:
Вместо глобального расширения прототипа часто применяется функциональная композиция через фабрики.
const createPasswordSchema = (minLength = 8) =>
Yup.string()
.min(minLength)
.matches(/[A-Z]/, 'Должна быть заглавная буква')
.matches(/[0-9]/, 'Должна быть цифра');
Использование фабрик позволяет:
В сложных структурах данные часто представляют собой массивы однотипных объектов. Yup позволяет комбинировать схемы для элементов массива.
const itemSchema = Yup.object({
name: Yup.string().required(),
price: Yup.number().positive()
});
const listSchema = Yup.array()
.of(itemSchema)
.min(1)
Композиция здесь выражается в повторном применении одной схемы к множеству элементов.
Метод when позволяет динамически переключать или
дополнять схему в зависимости от значений других полей.
const schema = Yup.object({
type: Yup.string().required(),
taxId: Yup.string().when('type', {
is: 'company',
then: schema => schema.required(),
otherwise: schema => schema.notRequired()
})
});
Этот механизм часто используется для построения адаптивных схем, где структура зависит от контекста данных.
lazy используется для ситуаций, когда структура данных
известна только во время выполнения.
const schema = Yup.lazy(value => {
if (Array.isArray(value)) {
return Yup.array().of(Yup.number());
}
return Yup.object({
value: Yup.number()
});
});
Такой подход позволяет реализовывать рекурсивные и полиморфные схемы.
Один из ключевых паттернов композиции — выделение базовых блоков в отдельные переменные.
const requiredString = Yup.string().required();
const positiveNumber = Yup.number().positive();
const productSchema = Yup.object({
title: requiredString,
price: positiveNumber
});
Преимущества такого подхода:
Вместо полного описания структуры часто применяется постепенное расширение:
const baseSchema = Yup.object({
id: Yup.string().required()
});
const withTimestamps = baseSchema.shape({
createdAt: Yup.date(),
updatedAt: Yup.date()
});
const withMeta = withTimestamps.shape({
meta: Yup.object({
source: Yup.string()
})
});
Каждый слой добавляет новый уровень ответственности, не затрагивая предыдущие правила.
При росте проекта схемы обычно группируются по уровням абстракции:
Композиция между этими уровнями формирует устойчивую структуру валидации.
При использовании композиционных механизмов Yup важно учитывать несколько особенностей:
concatshape не удаляет поля, а только добавляет или
переопределяетaddMethod влияет на глобальный контекст библиотекиЭти особенности требуют строгого контроля структуры схем, особенно в больших проектах с переиспользуемой валидацией.