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

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

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

  • композицию схем (concat)
  • фабрики схем (функции, возвращающие yup-схемы)
  • расширение типов через addMethod
  • использование частичных схем (object().shape с общими фрагментами)
  • условную логику (when)
  • ленивые схемы (lazy)

Выделение повторяющихся валидаторов в функции

Один из самых практичных подходов — создание функций, возвращающих готовые схемы.

import * as yup fr om 'yup';

export const emailValidator = () =>
  yup
    .string()
    .trim()
    .email('Некорректный email')
    .required('Email обязателен');

export const passwordValidator = () =>
  yup
    .string()
    .min(8, 'Минимум 8 символов')
    .matches(/[A-Z]/, 'Должна быть хотя бы одна заглавная буква')
    .matches(/[0-9]/, 'Должна быть хотя бы одна цифра')
    .required('Пароль обязателен');

Такой подход позволяет:

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

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

const schema = yup.object({
  email: emailValidator(),
  password: passwordValidator(),
});

Композиция схем через concat

concat позволяет объединять две схемы одного типа в одну.

const baseString = yup.string().trim();

const nameSchema = baseString.concat(
  yup.string().min(2, 'Слишком короткое имя')
);

Это особенно полезно, когда есть:

  • базовые правила (trim, nullable, default)
  • специфические ограничения для разных случаев

Общие фрагменты объектов

При работе с объектами часто выделяется базовая структура.

const timestamps = {
  createdAt: yup.date().required(),
  updatedAt: yup.date().required(),
};

const baseUserSchema = yup.object({
  id: yup.number().required(),
  ...timestamps,
});

Дальнейшее расширение:

const adminSchema = baseUserSchema.shape({
  role: yup.string().oneOf(['admin']).required(),
});

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


Расширение Yup через addMethod

addMethod позволяет добавлять собственные валидаторы ко всем схемам определённого типа.

import * as yup from 'yup';

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

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

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

Важно учитывать:

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

Параметризованные фабрики схем

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

const minLengthString = (min) =>
  yup.string().min(min, `Минимум ${min} символов`).required();

const usernameSchema = minLengthString(3);

Более сложный пример:

const rangeNumber = (min, max) =>
  yup
    .number()
    .min(min, `Минимум ${min}`)
    .max(max, `Максимум ${max}`)
    .required();

Такой стиль делает схемы:

  • конфигурируемыми
  • повторно используемыми
  • компактными

Условные переиспользуемые схемы

Метод when позволяет включать повторно используемые правила в зависимости от контекста.

const optionalEmail = yup.string().when('subscribe', {
  is: true,
  then: (schema) => emailValidator(),
  otherwise: (schema) => schema.notRequired(),
});

Здесь уже существующий валидатор внедряется в условную логику без дублирования.


Ленивые схемы для динамических структур

lazy применяется, когда структура данных зависит от значения.

const dynamicSchema = yup.lazy((value) => {
  if (typeof value === 'string') {
    return yup.string().min(3);
  }

  if (typeof value === 'number') {
    return yup.number().min(0);
  }

  return yup.mixed();
});

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


Библиотеки схем как набор строительных блоков

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

  • emailValidator
  • phoneValidator
  • idValidator
  • timestampFields
  • paginationSchema

Пример композиции:

const paginationSchema = yup.object({
  page: yup.number().min(1).required(),
  lim it: yup.number().min(1).max(100).required(),
});

const searchSchema = paginationSchema.shape({
  query: yup.string().min(1).required(),
});

Разделение схем по доменам

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

/validation
  /common
    stringSchemas.js
    numberSchemas.js
  /user
    userSchemas.js
  /order
    orderSchemas.js

Общие схемы становятся зависимостями для доменных модулей, что снижает связанность.


Переиспользование через partial schemas

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

const addressPart = {
  city: yup.string().required(),
  street: yup.string().required(),
};

const userWithAddress = yup.object({
  name: yup.string().required(),
  ...addressPart,
});

При росте проекта такие блоки становятся стандартными строительными элементами.


Изоляция бизнес-правил в валидаторах

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

export const adultAgeValidator = () =>
  yup
    .number()
    .min(18, 'Требуется совершеннолетие')
    .max(120, 'Некорректный возраст');

Использование в разных схемах:

const registrationSchema = yup.object({
  age: adultAgeValidator(),
});

Комбинирование схем разных уровней

Сложные структуры строятся из простых:

const baseProduct = yup.object({
  id: yup.number().required(),
  title: yup.string().required(),
});

const pricedProduct = baseProduct.shape({
  price: yup.number().positive().required(),
});

const discountedProduct = pricedProduct.shape({
  discount: yup.number().min(0).max(100),
});

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


Управление повторным использованием в больших проектах

При масштабировании важно соблюдать несколько технических принципов:

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

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