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

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


Базовая семантика concat

Метод concat выполняет объединение двух совместимых схем, формируя новую схему, содержащую правила обеих исходных. При этом одна из схем выступает базовой, а вторая — расширяющей.

import * as Yup from 'yup';

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

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

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


Совместимость типов схем

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

Корректные примеры:

Yup.string().concat(Yup.string().required());

Yup.number().concat(Yup.number().positive());

Некорректный пример:

Yup.string().concat(Yup.number()); // приведёт к ошибке

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


Порядок применения правил

При объединении схем через concat важен порядок: правила базовой схемы применяются первыми, затем добавляются правила расширяющей схемы.

const schema = Yup.string()
  .min(2, 'base min')
  .concat(
    Yup.string().min(5, 'extended min')
  );

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


Перекрытие правил и приоритет

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

Пример:

const schema = Yup.number()
  .min(1, 'base min')
  .concat(
    Yup.number().min(10, 'extended min')
  );

Фактически итоговое ограничение будет min(10), так как оно более строгое.


Расширение объектных схем

Наиболее практическое применение concat проявляется при работе с объектами. Объектные схемы позволяют поэтапно собирать структуру данных.

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

const profileExtension = Yup.object({
  name: Yup.string().required(),
  age: Yup.number().min(18)
});

const fullSchema = baseUserSchema.concat(profileExtension);

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


Поведение при дублировании ключей

Если в двух объединяемых объектных схемах присутствуют одинаковые ключи, поведение становится зависимым от порядка объединения. Значение схемы из второй (расширяющей) структуры заменяет первую.

const base = Yup.object({
  role: Yup.string().oneOf(['user', 'admin'])
});

const override = Yup.object({
  role: Yup.string().oneOf(['admin'])
});

const schema = base.concat(override);

В данном случае поле role будет ограничено только значением 'admin'.


Композиция через цепочку concat

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

const schema = Yup.string()
  .min(3)
  .concat(Yup.string().max(20))
  .concat(Yup.string().matches(/^[a-z]+$/));

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


Использование с кастомными схемами

concat работает не только с базовыми типами, но и с пользовательскими валидаторами, созданными через test.

const base = Yup.string().test(
  'no-spaces',
  'Не должно содержать пробелов',
  value => !/\s/.test(value)
);

const extended = Yup.string().min(5);

const schema = base.concat(extended);

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


Ограничения механизма concat

Несмотря на гибкость, механизм имеет ряд особенностей:

  • невозможность объединения несовместимых типов схем;
  • отсутствие глубокой интеллектуальной “склейки” правил (логика объединения довольно прямолинейна);
  • потенциальное перезаписывание конфликтующих правил в объектах;
  • невозможность частичного объединения сложных вложенных структур без явного контроля.

Практика модульного построения схем

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

  • базовая схема сущности;
  • расширения для разных сценариев (создание, обновление, админ-панель);
  • динамическое добавление правил.
const baseProduct = Yup.object({
  id: Yup.number().required(),
  title: Yup.string().required()
});

const pricingExtension = Yup.object({
  price: Yup.number().positive().required()
});

const adminExtension = Yup.object({
  internalCode: Yup.string()
});

const productSchema = baseProduct
  .concat(pricingExtension)
  .concat(adminExtension);

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


Поведение с nullable и optional значениями

При объединении схем с разными модификаторами (nullable, optional) итоговое поведение зависит от более строгой конфигурации.

const a = Yup.string().nullable();
const b = Yup.string().required();

const schema = a.concat(b);

Результатом будет схема с обязательным значением, так как required имеет приоритет над nullable в контексте проверки.


Влияние concat на трансформации (transform)

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

const trimSchema = Yup.string().transform(v => v.trim());
const lowerSchema = Yup.string().transform(v => v.toLowerCase());

const schema = trimSchema.concat(lowerSchema);

В этом случае сначала выполняется trim, затем toLowerCase.


Архитектурное применение валидационной композиции

Использование concat позволяет строить слоистую архитектуру валидации, где:

  • базовые правила инкапсулируют доменную логику;
  • расширения адаптируют её под интерфейс или API;
  • итоговые схемы формируются динамически.

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