Библиотека Yup построена вокруг концепции схемной валидации, где базовые типы (string, number, object, array) предоставляют ядро, а расширение поведения достигается через модификацию прототипов схем и добавление пользовательских методов. Такой подход позволяет формировать слой переиспользуемых правил, который в практике разработки часто называют «плагинной системой», хотя формально отдельного API для плагинов в Yup нет.
Расширения реализуются через несколько механизмов:
addMethod — добавление собственных методов к
схемамtest — регистрация пользовательских валидаторовtransform — предобработка входных данныхmixed и обобщённые типыОсновной способ создания плагинов — добавление методов в прототипы схем. Это позволяет внедрять доменные правила в виде цепочечных вызовов.
import * as Yup from "yup";
Yup.addMethod(Yup.string, "onlyDigits", function (message) {
return this.test("onlyDigits", message, function (value) {
if (value == null) return true;
return /^\d+$/.test(value);
});
});
Использование:
const schema = Yup.object({
phone: Yup.string().onlyDigits("Допустимы только цифры"),
});
Механизм основан на том, что this внутри метода
ссылается на текущий инстанс схемы, что позволяет сохранять цепочку
валидаторов и расширять её без разрушения базовой логики.
Наиболее частый сценарий — расширение строковых схем для нормализации данных и проверки бизнес-ограничений.
Yup.addMethod(Yup.string, "trimmed", function () {
return this.transform((value) =>
typeof value === "string" ? value.trim() : value
);
});
Yup.addMethod(Yup.string, "noSpaces", function (message) {
return this.test("noSpaces", message, (value) =>
value ? !/\s/.test(value) : true
);
});
Комбинирование таких методов формирует декларативные цепочки:
const schema = Yup.object({
username: Yup.string().trimmed().noSpaces(),
});
Числовые расширения часто используются для доменной валидации: диапазоны, шаги, ограничения финансовых значений.
Yup.addMethod(Yup.number, "isMultipleOf", function (factor, message) {
return this.test("isMultipleOf", message, (value) => {
if (value == null) return true;
return value % factor === 0;
});
});
const schema = Yup.object({
quantity: Yup.number().isMultipleOf(5, "Кратно пяти"),
});
Метод test является ядром плагинной архитектуры. Он
позволяет создавать динамические проверки с доступом к контексту схемы и
родительским значениям.
Yup.addMethod(Yup.string, "startsWithUpper", function (message) {
return this.test("startsWithUpper", message, function (value) {
if (!value) return true;
return /^[A-ZА-Я]/.test(value);
});
});
Более сложные сценарии используют this.parent:
Yup.addMethod(Yup.string, "matchField", function (fieldName, message) {
return this.test("matchField", message, function (value) {
return value === this.parent[fieldName];
});
});
Механизм transform позволяет вмешиваться в данные до
выполнения валидации. Это часто используется для нормализации входа.
Yup.addMethod(Yup.string, "toLower", function () {
return this.transform((value) =>
typeof value === "string" ? value.toLowerCase() : value
);
});
Такой подход снижает необходимость дублирования логики в бизнес-слое.
Object-схемы применяются для агрегации логики и условной валидации. Плагины здесь часто работают как генераторы структурных правил.
Yup.addMethod(Yup.object, "atLeastOne", function (fields, message) {
return this.test("atLeastOne", message, function (value) {
if (!value) return false;
return fields.some((field) => Boolean(value[field]));
});
});
Пример использования:
const schema = Yup.object({
email: Yup.string(),
phone: Yup.string(),
}).atLeastOne(["email", "phone"], "Нужно заполнить хотя бы одно поле");
Плагины в Yup не изолированы. Они комбинируются через цепочку вызовов, что формирует декларативные пайплайны обработки данных.
const schema = Yup.object({
password: Yup.string()
.trimmed()
.min(8)
.noSpaces()
.startsWithUpper("Первая буква должна быть заглавной"),
});
Порядок применения методов имеет значение, поскольку
transform выполняется до test, а валидаторы
выполняются последовательно.
Практика выделения плагинов в отдельные модули позволяет формировать слой доменной валидации.
// validators/stringPlugins.js
export function applyStringPlugins(Yup) {
Yup.addMethod(Yup.string, "slug", function () {
return this.transform((value) =>
typeof value === "string"
? value.toLowerCase().replace(/\s+/g, "-")
: value
);
});
}
import * as Yup from "yup";
import { applyStringPlugins } from "./validators/stringPlugins";
applyStringPlugins(Yup);
Использование when в сочетании с кастомными методами
расширяет поведение плагинов до контекстной логики.
Yup.addMethod(Yup.string, "requiredIf", function (field, value) {
return this.when(field, {
is: value,
then: (schema) => schema.required(),
});
});
mixed используется как универсальная точка расширения,
когда тип данных заранее неизвестен.
Yup.addMethod(Yup.mixed, "notNullOrUndefined", function (message) {
return this.test("notNullOrUndefined", message, (value) =>
value !== null && value !== undefined
);
});
При использовании Yup в TypeScript требуется расширение деклараций типов для сохранения автокомплита.
declare module "yup" {
interface StringSchema {
onlyDigits(message?: string): this;
trimmed(): this;
}
}
Без декларативного расширения типизация теряет информацию о добавленных методах.
Плагины группируются по функциональным областям:
Разделение этих слоёв позволяет избегать смешивания инфраструктурной и доменной логики внутри схем.
Механизм расширений через прототипы приводит к нескольким особенностям:
Эти аспекты требуют централизованного управления регистрацией расширений и строгой организации модулей в проекте.