Переиспользование валидационной логики

Валидационные схемы в экосистеме Yup и связке с YupResolver часто становятся центральным элементом бизнес-логики форм. При росте приложения дублирование правил валидации приводит к расхождению поведения между формами, усложняет сопровождение и повышает риск несогласованности данных. Повторное использование валидационной логики позволяет централизовать правила, строить композиции схем и формировать единый источник истины для проверки данных.

Ключевая идея заключается в том, что схема перестаёт быть локальным объектом конкретной формы и превращается в переиспользуемый модуль, который может расширяться, комбинироваться и адаптироваться под разные сценарии.


Базовые схемы как фундамент повторного использования

Наиболее простой уровень повторного использования — выделение базовых схем, описывающих сущности предметной области. Такие схемы содержат только универсальные правила, не привязанные к конкретной форме.

Пример базовой схемы пользователя:

import * as Yup from "yup";

export const userBaseSchema = Yup.object({
  email: Yup.string()
    .email("Некорректный email")
    .required("Email обязателен"),

  password: Yup.string()
    .min(8, "Минимум 8 символов")
    .required("Пароль обязателен"),
});

Данная схема может использоваться как основа для регистрации, авторизации или профиля, но в каждом случае расширяется дополнительными полями или правилами.

Повторное использование достигается за счёт неизменности базовой структуры и отсутствия контекста формы внутри неё.


Расширение схем через композицию

Композиция позволяет создавать новые схемы на основе существующих, добавляя или переопределяя отдельные поля.

Использование .concat

Yup предоставляет механизм объединения схем:

export const registrationSchema = userBaseSchema.concat(
  Yup.object({
    confirmPassword: Yup.string()
      .oneOf([Yup.ref("password")], "Пароли не совпадают")
      .required("Подтверждение обязательно"),
  })
);

Такой подход сохраняет базовые правила и добавляет контекст регистрации.

Важно учитывать, что concat работает на уровне объектов схем и может приводить к конфликтам при совпадении ключей, если не контролировать порядок объединения.


Фабрики схем для параметрической валидации

Более гибкий подход — использование функций-фабрик, возвращающих схемы на основе входных параметров. Это позволяет адаптировать правила под условия интерфейса или бизнес-логики.

Условная генерация схем

export const createPasswordSchema = ({ strong = false } = {}) => {
  return Yup.string()
    .required("Пароль обязателен")
    .min(8, "Минимум 8 символов")
    .test(
      "strength",
      "Пароль слишком слабый",
      value => {
        if (!strong) return true;
        return /[A-Z]/.test(value) && /[0-9]/.test(value);
      }
    );
};

Использование:

const schema = Yup.object({
  password: createPasswordSchema({ strong: true }),
});

Такой подход устраняет необходимость дублирования похожих правил с небольшими различиями.


Переиспользование через частичные схемы

Частичные схемы позволяют выделять логические блоки полей и комбинировать их в различных формах.

Пример разделения доменной модели

export const addressSchema = Yup.object({
  country: Yup.string().required(),
  city: Yup.string().required(),
  street: Yup.string().required(),
});
export const profileSchema = Yup.object({
  firstName: Yup.string().required(),
  lastName: Yup.string().required(),
}).concat(addressSchema);

Такое разделение снижает связность и позволяет использовать addressSchema в множестве сценариев: доставка, профиль, оформление заказа.


Переиспользование через .shape и расширение объектов

При работе с объектными схемами можно формировать расширения через доступ к shape и создание новых объектов на его основе.

const baseShape = {
  email: Yup.string().email().required(),
};

const extendedSchema = Yup.object({
  ...baseShape,
  role: Yup.string().oneOf(["user", "admin"]).required(),
});

Этот подход ближе к функциональному расширению и удобен при динамической сборке схем.


Общие валидаторы как строительные блоки

Выделение валидаторов на уровень функций позволяет стандартизировать правила и переиспользовать их в любых схемах.

Пример универсальных правил

export const requiredString = (message = "Обязательное поле") =>
  Yup.string().required(message);

export const emailField = () =>
  Yup.string().email("Некорректный email").required("Email обязателен");

Использование:

const schema = Yup.object({
  email: emailField(),
  name: requiredString("Имя обязательно"),
});

Такой подход уменьшает когнитивную нагрузку и делает схемы декларативными.


Переиспользование через условную логику

Сложные формы часто требуют изменения валидации в зависимости от состояния данных. Вместо дублирования схем используется условная композиция.

.when как инструмент адаптации

const schema = Yup.object({
  hasCompany: Yup.boolean(),

  companyName: Yup.string().when("hasCompany", {
    is: true,
    then: schema => schema.required("Название компании обязательно"),
    otherwise: schema => schema.notRequired(),
  }),
});

Условная логика позволяет использовать одну и ту же структуру формы в разных сценариях без создания отдельных схем.


Переиспользование через вложенные схемы

При работе с массивами и сложными структурами логика валидации часто повторяется на уровне элементов.

const productSchema = Yup.object({
  title: Yup.string().required(),
  price: Yup.number().positive().required(),
});

const orderSchema = Yup.object({
  items: Yup.array().of(productSchema).min(1),
});

Здесь productSchema становится универсальной единицей, используемой в различных контекстах: корзина, заказ, каталог.


Централизация доменной валидации

В крупных приложениях схемы начинают отражать бизнес-правила, а не структуру форм. В этом случае важно отделять доменную валидацию от UI-уровня.

Пример разделения:

// domain/validation/userRules.js
export const emailRule = Yup.string().email().required();
export const passwordRule = Yup.string().min(8).required();
// form schemas
export const loginSchema = Yup.object({
  email: emailRule,
  password: passwordRule,
});

Такое разделение делает доменную логику независимой от библиотек форм и позволяет использовать её вне UI.


Переиспользование в связке с YupResolver

В контексте YupResolver повторное использование схем напрямую влияет на поведение форм, так как резолвер работает с готовой схемой без дополнительной логики.

import { yupResolver } from "@hookform/resolvers/yup";

const resolver = yupResolver(loginSchema);

При изменении базовых схем автоматически обновляется поведение всех форм, использующих эти схемы, что усиливает эффект централизованной валидации.


Паттерн слоистой валидации

Для сложных систем применяется разделение на уровни:

  • базовые примитивы (string, number, boolean правила)
  • доменные правила (email, password strength)
  • сущностные схемы (user, product)
  • сценарные схемы (login, registration, checkout)

Пример слоистой структуры:

// primitives
const requiredString = () => Yup.string().required();

// domain
const email = () => Yup.string().email().required();

// entity
const user = Yup.object({
  email: email(),
  name: requiredString(),
});

// scenario
const signup = user.concat(
  Yup.object({
    password: Yup.string().min(8).required(),
  })
);

Такая структура снижает дублирование и упрощает масштабирование.


Избежание дублирования при изменении требований

При изменении бизнес-логики важно не модифицировать каждую схему отдельно. Вместо этого изменения вносятся на уровне базовых правил или фабрик.

Например, изменение политики паролей:

export const passwordRule = () =>
  Yup.string()
    .min(12, "Минимум 12 символов")
    .matches(/[A-Z]/, "Требуется заглавная буква")
    .required();

Все формы автоматически наследуют новое поведение без дополнительного рефакторинга.


Композиция массивов правил

Иногда требуется динамически собирать массивы правил:

const rules = [
  Yup.string().required(),
  Yup.string().email(),
];

const schema = Yup.string().concat(rules[1]);

Хотя такой подход используется реже, он полезен при генерации схем на основе конфигураций.


Переиспользование в генеративных формах

При построении форм на основе конфигураций схемы становятся результатом вычислений:

const buildField = config => {
  let schema = Yup.string();

  if (config.required) {
    schema = schema.required();
  }

  if (config.type === "email") {
    schema = schema.email();
  }

  return schema;
};

const schema = Yup.object({
  email: buildField({ type: "email", required: true }),
});

Такой подход позволяет строить формы без дублирования логики валидации.


Ограничения повторного использования

Чрезмерная абстракция может привести к снижению читаемости схем. Слишком глубокая композиция или большое количество фабрик усложняет понимание фактических правил валидации на уровне конкретной формы.

Баланс достигается через разделение:

  • простые формы — прямые схемы
  • повторяющиеся правила — фабрики
  • сложные сущности — базовые схемы
  • вариативные сценарии — композиция и .when

Поддержание согласованности схем

При активном переиспользовании важна строгая дисциплина:

  • единые правила именования полей
  • централизованные валидаторы
  • отсутствие дублирующих схем для одной сущности
  • контроль версий доменных правил

Такая организация предотвращает расхождение логики между различными частями приложения и обеспечивает предсказуемость поведения YupResolver во всех формах.