В основе библиотеки Yup лежит абстракция схемы валидации,
реализованная через базовый класс Schema. Именно он
формирует поведение всех производных типов: string,
number, boolean, object,
array, а также пользовательских схем. Архитектура
библиотеки построена таким образом, что расширение функциональности чаще
происходит не через классическое наследование, а через композицию,
регистрацию методов и подключение кастомных проверок.
Schema определяет общий контракт для всех типов
валидации:
tests)transforms)ValidationContext)Каждая конкретная схема в Yup фактически является специализированной конфигурацией над этим базовым поведением.
Пример логики, характерной для любого типа:
import * as yup from 'yup';
const schema = yup.string().required().min(3);
schema.validate('ab')
.catch(err => {
console.log(err.errors);
});
Здесь цепочка .required().min(3) не изменяет класс, а
добавляет тесты в внутренний список Schema.
Вместо прямого наследования чаще используются три механизма:
addMethodmixed() как базового расширяемого
типаconcat и вложенные схемыФункция addMethod позволяет расширять прототип любого
типа схемы или базового Schema. Это основной инструмент
расширения поведения библиотеки.
yup.addMethod(yup.string, 'startsWithA', function (message) {
return this.test('starts-with-a', message, function (value) {
if (!value) return true;
return value.startsWith('A');
});
});
Использование:
const schema = yup.string().startsWithA('Строка должна начинаться с A');
schema.validate('Hello'); // ошибка
schema.validate('Apple'); // ok
addMethod модифицирует прототип схемыthisТакой подход позволяет расширять поведение без изменения исходного кода библиотеки.
mixed() — наиболее универсальная схема. Она используется
как фундамент для создания кастомных валидаторов.
const customSchema = yup.mixed().test(
'is-positive-id',
'ID должен быть положительным числом',
(value) => typeof value === 'number' && value > 0
);
Этот механизм удобен для создания полностью новых правил, не зависящих от встроенных типов.
const uuidSchema = yup.mixed().test(
'is-uuid',
'Неверный UUID',
(value) => {
const regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return typeof value === 'string' && regex.test(value);
}
);
Хотя библиотека не ориентирована на классическое ООП-наследование,
технически возможно создать собственный класс, унаследованный от
Schema.
import { Schema } from 'yup';
class PositiveNumberSchema extends Schema {
constructor() {
super({
type: 'positive-number'
});
}
_typeCheck(value) {
return typeof value === 'number' && value > 0;
}
}
Такой подход используется редко, поскольку:
_typeCheckТем не менее он позволяет создать полностью новый тип схемы с уникальным поведением.
concat позволяет объединять две схемы, сохраняя все
правила обеих.
const base = yup.string().min(3);
const extended = yup.string().matches(/^[A-Z]/);
const result = base.concat(extended);
В результате:
baseextendedЭтот механизм часто используется как безопасная альтернатива наследованию.
Архитектура Yup делает акцент на композиции схем, а не на расширении классов.
const passwordSchema = yup.string()
.min(8)
.matches(/[A-Z]/)
.matches(/[0-9]/)
.required();
Каждый вызов метода возвращает новый экземпляр Schema,
расширяя цепочку.
Метод test является универсальным инструментом
расширения логики:
const schema = yup.number().test(
'is-even',
'Число должно быть чётным',
(value) => value % 2 === 0
);
Контекст теста позволяет работать с зависимыми значениями:
yup.object({
min: yup.number(),
max: yup.number().test(
'greater-than-min',
'max должен быть больше min',
function (value) {
return value > this.parent.min;
}
)
});
В TypeScript-окружении расширение Schema требует синхронизации типов:
declare module 'yup' {
interface StringSchema {
startsWithA(message?: string): StringSchema;
}
}
И реализация:
yup.addMethod(yup.string, 'startsWithA', function (message) {
return this.test('starts-with-a', message, value =>
!value || value.startsWith('A')
);
});
Это позволяет сохранять типобезопасность при расширении API.
На практике используются несколько устойчивых подходов:
1. Библиотека расширений
addMethod-расширений2. Фабрики схем
const createPositiveNumber = () =>
yup.number().test(
'positive',
'must be positive',
v => v > 0
);
3. Слоистые схемы
Несмотря на гибкость, существуют архитектурные ограничения:
testsЭти ограничения объясняют, почему библиотека делает упор на композицию и функциональное расширение, а не на классическое ООП.
Каждый добавленный тест хранится как объект с метаданными:
exclusive)При вызове validate происходит последовательный проход
по цепочке:
Именно эта структура делает расширение предсказуемым и повторяемым независимо от сложности схемы.