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

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

Повторное использование схем в Yup строится вокруг идеи того, что каждая схема — это обычный объект с методами, который можно:

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

Ключевой момент заключается в том, что схема не является «одноразовой конструкцией», а представляет собой композиционный элемент.

Выделение базовых схем

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

import * as yup from "yup";

const baseString = yup
  .string()
  .trim()
  .min(2, "Слишком короткое значение")
  .max(50, "Слишком длинное значение");

Теперь эта базовая схема может использоваться в разных контекстах:

const userSchema = yup.object().shape({
  firstName: baseString.required(),
  lastName: baseString.required(),
});

Подход позволяет централизованно изменять базовую логику: например, если требуется изменить минимальную длину строки, это делается в одном месте.

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

Часто данные состоят из повторяющихся структур. Например, адрес:

const addressSchema = yup.object().shape({
  country: yup.string().required(),
  city: yup.string().required(),
  street: yup.string().required(),
});

Эта схема может быть встроена в другие схемы:

const userSchema = yup.object().shape({
  name: yup.string().required(),
  address: addressSchema.required(),
});

Таким образом создаётся иерархическая структура, где каждый блок отвечает за свою часть валидации.

Расширение схем через concat

Метод .concat() позволяет объединять схемы одного типа. Это особенно полезно, когда нужно расширить базовую схему дополнительными правилами.

const baseEmail = yup.string().email("Некорректный email");

const strictEmail = baseEmail.concat(
  yup.string().matches(/@company\.com$/, "Только корпоративная почта")
);

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

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

Фабрики схем: генерация через функции

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

const createMinMaxString = (min, max) =>
  yup.string().min(min).max(max);

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

const productSchema = yup.object().shape({
  title: createMinMaxString(5, 100).required(),
  description: createMinMaxString(20, 500),
});

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

Переиспользование через partial-структуры

Хотя Yup не предоставляет встроенных методов pick или omit, аналогичный эффект достигается за счёт композиции объектов.

const credentialsSchema = yup.object().shape({
  email: yup.string().email().required(),
  password: yup.string().min(8).required(),
});

Эта схема может использоваться как часть других схем:

const loginSchema = credentialsSchema;

const registrationSchema = credentialsSchema.concat(
  yup.object().shape({
    confirmPassword: yup.string().oneOf([yup.ref("password")]),
  })
);

Здесь базовая логика авторизации переиспользуется без копирования.

Условное переиспользование через when

Иногда одна и та же схема должна вести себя по-разному в зависимости от условий. Для этого используется .when().

const paymentSchema = yup.object().shape({
  method: yup.string().required(),
  cardNumber: yup.string().when("method", {
    is: "card",
    then: (schema) => schema.required().min(16),
    otherwise: (schema) => schema.notRequired(),
  }),
});

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

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

Повторяющиеся правила удобно выносить в отдельные функции:

const requiredString = (message = "Поле обязательно") =>
  yup.string().required(message);

const positiveNumber = () =>
  yup.number().positive("Значение должно быть положительным");

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

const orderSchema = yup.object().shape({
  name: requiredString(),
  price: positiveNumber(),
});

Это особенно полезно в больших проектах, где единообразие сообщений критично.

Композиция вложенных схем

Сложные структуры часто требуют многократного переиспользования вложенных объектов:

const geoSchema = yup.object().shape({
  lat: yup.number().required(),
  lng: yup.number().required(),
});

const warehouseSchema = yup.object().shape({
  location: geoSchema,
  name: yup.string().required(),
});

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

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

Схемы можно переиспользовать и внутри массивов:

const tagSchema = yup.string().min(2);

const postSchema = yup.object().shape({
  title: yup.string().required(),
  tags: yup.array().of(tagSchema),
});

Если логика валидации тега изменится, обновление затронет все места использования.

Стратегия централизованных схем

В больших кодовых базах часто создаётся отдельный слой схем:

  • базовые типы (string, number, boolean)
  • составные блоки (address, user, auth)
  • доменные схемы (order, product, payment)

Пример структуры:

// schemas/base.js
export const baseString = yup.string().trim();

// schemas/address.js
export const addressSchema = yup.object().shape({...});

// schemas/user.js
export const userSchema = yup.object().shape({...});

Такой подход снижает связанность и упрощает сопровождение.

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

Yup позволяет расширять функциональность через addMethod, что создаёт новые способы повторного использования логики.

import * as yup from "yup";

yup.addMethod(yup.string, "onlyDigits", function () {
  return this.matches(/^\d+$/, "Только цифры");
});

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

const schema = yup.object().shape({
  phone: yup.string().onlyDigits().required(),
});

Это превращает повторяющуюся логику в часть DSL.

Комбинирование схем в сложных формах

При работе с формами, содержащими несколько независимых блоков, удобно комбинировать схемы:

const profileSchema = yup.object().shape({
  personal: personalSchema,
  contacts: contactsSchema,
  preferences: preferencesSchema,
});

Каждый блок остаётся автономным, но участвует в общей валидации.

Переиспользование сообщений и правил

Часто вместе со схемами переиспользуются и сообщения:

const messages = {
  required: "Поле обязательно для заполнения",
  email: "Некорректный формат email",
};

const emailSchema = yup.string().email(messages.email).required(messages.required);

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

Итеративное расширение схем

Схемы можно развивать постепенно:

const baseUser = yup.object().shape({
  name: yup.string().required(),
});

const extendedUser = baseUser.concat(
  yup.object().shape({
    role: yup.string().required(),
  })
);

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

Закономерности проектирования схем

В зрелых проектах повторное использование схем обычно следует нескольким принципам:

  • выделение атомарных валидаторов
  • построение композиции через вложенность
  • отказ от дублирования через фабрики
  • централизованное хранение базовых схем
  • расширение через concat и addMethod

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