Миксины и композиция

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

Базовые принципы композиции схем

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

Основные инструменты композиции:

  • concat — объединение схем одного типа
  • object.shape — расширение структуры объекта
  • mixed и кастомные типы — базовые расширения
  • when — условная композиция
  • lazy — отложенное определение схем
  • addMethod — расширение прототипа (аналог миксинов)

Объединение схем через concat

Метод concat применяется для объединения двух схем одинакового типа. Он часто используется для наращивания базовой логики.

import * as Yup from 'yup';

const baseString = Yup.string()
  .min(3, 'Минимум 3 символа');

const withMaxLength = Yup.string()
  .max(10, 'Максимум 10 символов');

const composed = baseString.concat(withMaxLength);

Результирующая схема включает оба ограничения: минимальную и максимальную длину.

Важно учитывать, что concat работает только для совместимых типов. Попытка объединить string и number приведёт к ошибке.

Композиция объектов через shape

Для объектных схем основным механизмом расширения выступает shape. Он позволяет добавлять новые поля поверх базовой структуры.

const baseUserSchema = Yup.object({
  id: Yup.number().required(),
  email: Yup.string().email().required()
});

const extendedUserSchema = baseUserSchema.shape({
  role: Yup.string().oneOf(['admin', 'user']),
  createdAt: Yup.date()
});

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

Распространённый подход — выделение базовых сущностей:

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

Каждый следующий слой строится поверх предыдущего.

Миксины через addMethod

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

Yup.addMethod(Yup.string, 'startsWithUppercase', function (message) {
  return this.test('starts-with-uppercase', message, function (value) {
    if (!value) return true;
    return /^[A-Z]/.test(value);
  });
});

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

const schema = Yup.string()
  .startsWithUppercase('Должно начинаться с заглавной буквы');

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

Типичные сценарии миксинов:

  • проверка бизнес-правил
  • специфические форматы (ИНН, телефон, артикул)
  • локализованные ограничения

Фабрики схем как альтернатива миксинам

Вместо глобального расширения прототипа часто применяется функциональная композиция через фабрики.

const createPasswordSchema = (minLength = 8) =>
  Yup.string()
    .min(minLength)
    .matches(/[A-Z]/, 'Должна быть заглавная буква')
    .matches(/[0-9]/, 'Должна быть цифра');

Использование фабрик позволяет:

  • избегать глобального загрязнения API
  • параметризовать правила
  • создавать изолированные наборы валидаторов

Композиция через массивы схем

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

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

const listSchema = Yup.array()
  .of(itemSchema)
  .min(1)

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

Условная композиция с when

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

const schema = Yup.object({
  type: Yup.string().required(),
  taxId: Yup.string().when('type', {
    is: 'company',
    then: schema => schema.required(),
    otherwise: schema => schema.notRequired()
  })
});

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

Отложенная композиция через lazy

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

const schema = Yup.lazy(value => {
  if (Array.isArray(value)) {
    return Yup.array().of(Yup.number());
  }
  return Yup.object({
    value: Yup.number()
  });
});

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

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

Один из ключевых паттернов композиции — выделение базовых блоков в отдельные переменные.

const requiredString = Yup.string().required();
const positiveNumber = Yup.number().positive();

const productSchema = Yup.object({
  title: requiredString,
  price: positiveNumber
});

Преимущества такого подхода:

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

Композиция через расширение объектов схем

Вместо полного описания структуры часто применяется постепенное расширение:

const baseSchema = Yup.object({
  id: Yup.string().required()
});

const withTimestamps = baseSchema.shape({
  createdAt: Yup.date(),
  updatedAt: Yup.date()
});

const withMeta = withTimestamps.shape({
  meta: Yup.object({
    source: Yup.string()
  })
});

Каждый слой добавляет новый уровень ответственности, не затрагивая предыдущие правила.

Организация библиотеки схем

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

  • примитивные валидаторы (string, number, date)
  • доменные схемы (user, product, order)
  • составные схемы (forms, API payloads)

Композиция между этими уровнями формирует устойчивую структуру валидации.

Ограничения и особенности композиции

При использовании композиционных механизмов Yup важно учитывать несколько особенностей:

  • конфликтующие правила могут переопределять друг друга при concat
  • порядок вызовов методов влияет на итоговую схему
  • shape не удаляет поля, а только добавляет или переопределяет
  • addMethod влияет на глобальный контекст библиотеки

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