Yup предоставляет систему схем валидации, где типы и правила описываются декларативно. При построении сложных приложений возникает необходимость создавать универсальные (generic) схемы, которые могут переиспользоваться, параметризоваться и адаптироваться под различные структуры данных без дублирования логики.
Generic подход в схемах Yup выражается не через классические generics языка JavaScript, а через комбинацию фабрик схем, композиции, отложенного вычисления и возможностей TypeScript-интеграции.
Generic схема — это схема, которая:
В основе лежит функция-фабрика:
import * as yup from "yup";
const createStringSchema = (min = 0, max = 255) => {
return yup.string().min(min).max(max).required();
};
Такая конструкция позволяет абстрагировать правила и переиспользовать их в разных моделях данных.
Фабрики — основной способ создания generic-схем в Yup. Они позволяют инкапсулировать правила и управлять ими через аргументы.
const createNumberSchema = ({ min, max, integer = false }) => {
let schema = yup.number();
if (integer) {
schema = schema.integer();
}
if (min !== undefined) {
schema = schema.min(min);
}
if (max !== undefined) {
schema = schema.max(max);
}
return schema.required();
};
Такой подход особенно полезен для форм, где ограничения зависят от бизнес-логики.
Объектные схемы являются частым источником дублирования. Универсализация достигается через функцию, принимающую описание структуры.
const createUserSchema = ({ emailRequired = true }) => {
return yup.object({
name: yup.string().required(),
email: emailRequired ? yup.string().email().required() : yup.string().email(),
age: yup.number().min(0),
});
};
При расширении структуры можно комбинировать схемы.
Yup поддерживает композицию схем, что позволяет строить generic-модели через расширение базовых определений.
const baseString = yup.string().trim();
const emailSchema = baseString.concat(
yup.string().email("Некорректный email")
);
Это позволяет создавать слои абстракции: базовая схема → специализированная схема → доменная схема.
Yup предоставляет механизм lazy, который позволяет
определять схему во время выполнения. Это ключевой инструмент для
generic логики.
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().notRequired();
});
Lazy-схемы позволяют строить полиморфные структуры данных, где тип значения определяется в runtime.
Хотя Yup не предоставляет полноценные union-типы, их можно эмулировать через discriminator-поля.
const schema = yup.lazy((obj) => {
switch (obj?.type) {
case "admin":
return yup.object({
type: yup.string().oneOf(["admin"]),
permissions: yup.array().required(),
});
case "user":
return yup.object({
type: yup.string().oneOf(["user"]),
email: yup.string().email().required(),
});
default:
return yup.mixed().test("invalid-type", "Unknown type", () => false);
}
});
Такой подход формирует универсальную точку входа с различными вариантами структуры.
При использовании TypeScript Yup может работать с типами через inferencing и явные generics.
import * as yup from "yup";
const userSchema = yup.object({
name: yup.string().required(),
age: yup.number().required(),
});
type User = yup.InferType<typeof userSchema>;
const createArraySchema = <T>(itemSchema: yup.Schema<T>) => {
return yup.array().of(itemSchema);
};
Такая конструкция позволяет создавать типобезопасные универсальные схемы.
Часто создаются стандартные primitive-схемы, которые затем комбинируются.
const schemas = {
id: yup.string().uuid().required(),
createdAt: yup.date().required(),
email: yup.string().email().required(),
};
Затем они используются как строительные блоки:
const postSchema = yup.object({
id: schemas.id,
authorEmail: schemas.email,
createdAt: schemas.createdAt,
title: yup.string().required(),
});
Для повышения переиспользуемости создаются функции-обёртки:
const withRequired = (schema) => schema.required();
const withMinLength = (schema, length) => schema.min(length);
Комбинирование таких функций формирует декларативный DSL поверх Yup.
Generic подход особенно важен при работе с динамическими структурами, где поля неизвестны заранее.
const createDynamicObjectSchema = (shapeFactory) => {
return yup.object().shape(shapeFactory());
};
Пример использования:
const schema = createDynamicObjectSchema(() => ({
[fieldName]: yup.string().required(),
}));
Метод when позволяет параметризовать схему в зависимости
от других полей.
const schema = yup.object({
role: yup.string().required(),
accessCode: yup.string().when("role", {
is: "admin",
then: (s) => s.required(),
otherwise: (s) => s.notRequired(),
}),
});
Это формирует зависимые generic-правила внутри одной структуры.
Yup позволяет расширять API через addMethod, что также
используется для создания generic-расширений.
yup.addMethod(yup.string, "startsWithA", function () {
return this.test("starts-with-a", "Must start with A", (value) => {
return value?.startsWith("A");
});
});
После расширения метод становится частью всех строковых схем.
При масштабировании схемы начинают формироваться слои:
Такое разделение снижает связность и повышает повторное использование.
Несмотря на гибкость, существуют ограничения:
Эти ограничения компенсируются архитектурными паттернами и TypeScript-слоем.