Библиотека Yup построена вокруг композиции схем и позволяет не только
использовать встроенные типы (string, number,
array, object), но и расширять их поведение
через создание собственных правил валидации и переиспользуемых типов
схем.
Кастомизация схем в Yup обычно опирается на три ключевых механизма:
добавление пользовательских методов, использование test, а
также создание обобщённых (reusable) схем через mixed и
lazy.
Все типы в Yup наследуются от базового конструктора
mixed. Это означает, что создание нестандартных схем
начинается именно с него.
import * as Yup from 'yup';
const customSchema = Yup.mixed();
mixed не накладывает ограничений на тип значения, но
предоставляет механизм тестирования через .test() и
трансформации через .transform().
Основной способ добавления кастомной логики — метод
.test().
const evenNumberSchema = Yup.number().test(
'is-even',
'Число должно быть чётным',
(value) => value % 2 === 0
);
Структура test включает три элемента:
Функция теста получает дополнительный контекст:
Yup.string().test(
'min-with-context',
'Ошибка длины',
function (value) {
const { minLength } = this.options.context || {};
return value.length >= minLength;
}
);
Контекст позволяет создавать схемы, зависящие от внешних параметров.
Часто требуется создавать одинаковые схемы с разными параметрами. Для этого используются функции-фабрики:
const createRangeNumber = (min, max) =>
Yup.number()
.min(min)
.max(max)
.required();
Использование:
const ageSchema = createRangeNumber(18, 65);
const scoreSchema = createRangeNumber(0, 100);
Такой подход формирует слой абстракции над базовыми схемами и снижает дублирование логики.
Yup позволяет добавлять новые методы к существующим типам через
addMethod.
Yup.addMethod(Yup.string, 'startsWithCapital', function () {
return this.test(
'starts-with-capital',
'Строка должна начинаться с заглавной буквы',
(value) => /^[A-ZА-Я]/.test(value)
);
});
После добавления метод становится частью API:
const schema = Yup.string().startsWithCapital();
Yup.addMethod(Yup.number, 'isDivisibleBy', function (divisor) {
return this.test(
'is-divisible-by',
`Число должно делиться на ${divisor}`,
(value) => value % divisor === 0
);
});
mixed позволяет определить поведение, близкое к новому
типу данных.
const uuidSchema = Yup.mixed().test(
'is-uuid',
'Некорректный UUID',
(value) =>
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value)
);
Такой подход используется для:
Перед валидацией часто требуется привести данные к нужному виду.
const trimmedString = Yup.string().transform((value) =>
value ? value.trim() : value
);
Пример для чисел:
const numberFromString = Yup.number().transform((value, originalValue) => {
return typeof originalValue === 'string'
? Number(originalValue.replace(',', '.'))
: value;
});
lazy используется, когда структура данных зависит от
значения.
const schema = Yup.lazy((value) => {
if (typeof value === 'string') {
return Yup.string().min(3);
}
if (typeof value === 'number') {
return Yup.number().positive();
}
return Yup.mixed().notRequired();
});
Это позволяет реализовывать динамическую типизацию.
Схемы можно комбинировать для формирования сложных правил.
const baseString = Yup.string().trim().min(3);
const usernameSchema = baseString.matches(
/^[a-z0-9_]+$/,
'Недопустимые символы'
);
Композиция уменьшает дублирование и делает правила декларативными.
Кастомные схемы часто применяются внутри объектов:
const userSchema = Yup.object({
id: uuidSchema.required(),
name: Yup.string().startsWithCapital(),
age: createRangeNumber(18, 99),
});
Каждое поле может использовать собственный расширенный тип.
С помощью when можно комбинировать кастомные типы с
логикой зависимости.
const passwordSchema = Yup.string().when('isAdmin', {
is: true,
then: (schema) => schema.min(12),
otherwise: (schema) => schema.min(6),
});
Кастомные схемы могут быть асинхронными:
const emailExistsSchema = Yup.string().test(
'email-exists',
'Email уже используется',
async (value) => {
const response = await fetch(`/api/check-email?email=${value}`);
const data = await response.json();
return !data.exists;
}
);
Асинхронные проверки часто используются для:
const idSchema = Yup.string()
.matches(/^[a-z0-9-]+$/, 'Некорректный ID')
.min(5)
.max(50);
const emailSchema = Yup.string()
.transform((v) => v?.toLowerCase())
.email('Некорректный email');
const phoneSchema = Yup.string().test(
'phone-format',
'Некорректный номер',
(value) => /^\+?[0-9]{10,15}$/.test(value)
);
Кастомные типы становятся базовыми строительными блоками для сложных структур:
const orderSchema = Yup.object({
orderId: idSchema.required(),
userEmail: emailSchema.required(),
total: createRangeNumber(0, 100000),
contactPhone: phoneSchema,
});
Такой подход обеспечивает единый стандарт валидации по всему приложению и упрощает сопровождение логики.