Создание собственных типов схем

Библиотека Yup построена вокруг композиции схем и позволяет не только использовать встроенные типы (string, number, array, object), но и расширять их поведение через создание собственных правил валидации и переиспользуемых типов схем.

Кастомизация схем в Yup обычно опирается на три ключевых механизма: добавление пользовательских методов, использование test, а также создание обобщённых (reusable) схем через mixed и lazy.


Базовая модель расширения: mixed как основа кастомных типов

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

import * as Yup from 'yup';

const customSchema = Yup.mixed();

mixed не накладывает ограничений на тип значения, но предоставляет механизм тестирования через .test() и трансформации через .transform().


Использование метода test для создания собственных правил

Основной способ добавления кастомной логики — метод .test().

const evenNumberSchema = Yup.number().test(
  'is-even',
  'Число должно быть чётным',
  (value) => value % 2 === 0
);

Структура test включает три элемента:

  • уникальное имя теста;
  • сообщение об ошибке;
  • функцию проверки значения.

Доступ к контексту

Функция теста получает дополнительный контекст:

Yup.string().test(
  'min-with-context',
  'Ошибка длины',
  function (value) {
    const { minLength } = this.options.context || {};
    return value.length >= minLength;
  }
);

Контекст позволяет создавать схемы, зависящие от внешних параметров.


Создание переиспользуемых схем через фабрики

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

const createRangeNumber = (min, max) =>
  Yup.number()
    .min(min)
    .max(max)
    .required();

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

const ageSchema = createRangeNumber(18, 65);
const scoreSchema = createRangeNumber(0, 100);

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


Расширение встроенных типов через addMethod

Yup позволяет добавлять новые методы к существующим типам через addMethod.

Yup.addMethod(Yup.string, 'startsWithCapital', function () {
  return this.test(
    'starts-with-capital',
    'Строка должна начинаться с заглавной буквы',
    (value) => /^[A-ZА-Я]/.test(value)
  );
});

После добавления метод становится частью API:

const schema = Yup.string().startsWithCapital();

Расширение number с бизнес-логикой

Yup.addMethod(Yup.number, 'isDivisibleBy', function (divisor) {
  return this.test(
    'is-divisible-by',
    `Число должно делиться на ${divisor}`,
    (value) => value % divisor === 0
  );
});

Создание полностью кастомного типа через mixed

mixed позволяет определить поведение, близкое к новому типу данных.

const uuidSchema = Yup.mixed().test(
  'is-uuid',
  'Некорректный UUID',
  (value) =>
    /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value)
);

Такой подход используется для:

  • UUID;
  • HEX-цветов;
  • кастомных идентификаторов;
  • сложных строковых форматов.

Использование transform для нормализации данных

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

const trimmedString = Yup.string().transform((value) =>
  value ? value.trim() : value
);

Пример для чисел:

const numberFromString = Yup.number().transform((value, originalValue) => {
  return typeof originalValue === 'string'
    ? Number(originalValue.replace(',', '.'))
    : value;
});

Lazy-схемы для динамических типов

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

const schema = Yup.lazy((value) => {
  if (typeof value === 'string') {
    return Yup.string().min(3);
  }
  if (typeof value === 'number') {
    return Yup.number().positive();
  }
  return Yup.mixed().notRequired();
});

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


Композиция кастомных схем

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

const baseString = Yup.string().trim().min(3);

const usernameSchema = baseString.matches(
  /^[a-z0-9_]+$/,
  'Недопустимые символы'
);

Композиция уменьшает дублирование и делает правила декларативными.


Создание сложных объектных типов

Кастомные схемы часто применяются внутри объектов:

const userSchema = Yup.object({
  id: uuidSchema.required(),
  name: Yup.string().startsWithCapital(),
  age: createRangeNumber(18, 99),
});

Каждое поле может использовать собственный расширенный тип.


Условные кастомные правила

С помощью when можно комбинировать кастомные типы с логикой зависимости.

const passwordSchema = Yup.string().when('isAdmin', {
  is: true,
  then: (schema) => schema.min(12),
  otherwise: (schema) => schema.min(6),
});

Работа с асинхронной валидацией

Кастомные схемы могут быть асинхронными:

const emailExistsSchema = Yup.string().test(
  'email-exists',
  'Email уже используется',
  async (value) => {
    const response = await fetch(`/api/check-email?email=${value}`);
    const data = await response.json();
    return !data.exists;
  }
);

Асинхронные проверки часто используются для:

  • проверки уникальности;
  • обращений к API;
  • валидации внешних ресурсов.

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

Стандартизированный ID

const idSchema = Yup.string()
  .matches(/^[a-z0-9-]+$/, 'Некорректный ID')
  .min(5)
  .max(50);

Email с нормализацией

const emailSchema = Yup.string()
  .transform((v) => v?.toLowerCase())
  .email('Некорректный email');

Телефон с кастомной логикой

const phoneSchema = Yup.string().test(
  'phone-format',
  'Некорректный номер',
  (value) => /^\+?[0-9]{10,15}$/.test(value)
);

Интеграция кастомных типов в большие схемы

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

const orderSchema = Yup.object({
  orderId: idSchema.required(),
  userEmail: emailSchema.required(),
  total: createRangeNumber(0, 100000),
  contactPhone: phoneSchema,
});

Такой подход обеспечивает единый стандарт валидации по всему приложению и упрощает сопровождение логики.