Мутирование схем

Понимание природы изменений схем

Библиотека Yup строит систему валидации на основе цепочек и композиций, где каждая схема представляет собой объект с набором правил. Несмотря на то что многие операции выглядят как «изменение» существующей схемы, внутренне чаще происходит создание новой версии с расширенной логикой. Это приводит к важному различию между логическим мутированием (изменением поведения схемы) и физическим изменением объекта.

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

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

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

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

import * as Yup from 'yup';

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

const extendedSchema = baseSchema.shape({
  age: Yup.number().min(0),
});

Поведение shape важно понимать в контексте внутреннего устройства:

  • если поле отсутствует — оно добавляется;
  • если поле уже существует — оно переопределяется;
  • исходная схема может быть затронута в зависимости от версии реализации, что делает поведение ближе к «мягкому мутированию».

При работе с динамическими формами именно shape становится ключевым инструментом эволюции схемы без полного пересоздания структуры.


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

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

const schemaA = Yup.string().min(3);
const schemaB = Yup.string().max(10);

const merged = schemaA.concat(schemaB);

Особенности композиции:

  • правила обеих схем объединяются;
  • при конфликте приоритет обычно у последней схемы;
  • используется только для совместимых типов (string с string, object с object и т.д.).

В отличие от shape, concat ближе к функциональной композиции, а не к структурному изменению.


Динамическая логика через when

Одним из наиболее мощных механизмов «условного мутирования» является when. Он позволяет изменять поведение схемы в зависимости от внешних значений.

const schema = Yup.object({
  isCompany: Yup.boolean(),
  vatId: Yup.string().when('isCompany', {
    is: true,
    then: (s) => s.required(),
    otherwise: (s) => s.notRequired(),
  }),
});

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

Ключевые характеристики:

  • схема становится контекстно-зависимой;
  • правила формируются лениво;
  • результат зависит от входных данных, а не от статической структуры.

Ленивое построение схем через lazy

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

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

Особенности:

  • схема создаётся во время валидации;
  • возможна полная замена типа в зависимости от входных данных;
  • используется для рекурсивных структур (деревья, вложенные конфигурации).

Добавление пользовательских методов через addMethod

Глобальное расширение функциональности достигается через addMethod. Этот механизм изменяет поведение прототипов схем, фактически мутируя библиотеку на уровне типов.

Yup.addMethod(Yup.string, 'startsWithA', function () {
  return this.test('startsWithA', function (value) {
    return value?.startsWith('A');
  });
});

После этого метод становится доступным для всех строковых схем:

Yup.string().startsWithA();

Особенности:

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

Модификация поведения через test

Метод test является базовым способом добавления кастомной логики проверки. Хотя он не изменяет структуру схемы, он фактически «встраивает» дополнительное правило в существующую цепочку.

const schema = Yup.string().test(
  'is-even-length',
  'Длина должна быть чётной',
  (value) => value?.length % 2 === 0
);

Характеристика:

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

Клонирование и независимость схем

При композиции и расширении схем важно учитывать, что многие операции возвращают новые экземпляры. Это создаёт эффект «контролируемого копирования».

const base = Yup.string().min(3);
const variant = base.min(5);

В этом случае:

  • base остаётся неизменным;
  • variant представляет новую схему;
  • изменения не протекают обратно.

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


Управление объектными схемами через reach

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

const schema = Yup.object({
  user: Yup.object({
    profile: Yup.object({
      age: Yup.number(),
    }),
  }),
});

const ageSchema = Yup.reach(schema, 'user.profile.age');

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


Преобразование значений как форма логического мутирования

Метод transform изменяет входные данные до проверки, что косвенно влияет на поведение схемы.

const schema = Yup.number().transform((value, originalValue) => {
  return originalValue === '' ? null : value;
});

Здесь происходит:

  • нормализация данных;
  • изменение входного представления без изменения самой схемы;
  • подготовка данных к валидации.

Поведение stripUnknown и модификация структуры данных

Опция stripUnknown влияет на итоговую форму объекта, удаляя неизвестные поля.

const schema = Yup.object({
  name: Yup.string(),
}).noUnknown(true);

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


Контекст и влияние ref на динамическое изменение правил

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

const schema = Yup.object({
  password: Yup.string(),
  confirm: Yup.string().oneOf([Yup.ref('password')]),
});

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


Итоговая картина механизмов изменения схем

Мутирование схем в Yup складывается из нескольких уровней:

  • структурное расширение через shape;
  • композиция через concat;
  • динамическое ветвление через when и lazy;
  • глобальное расширение через addMethod;
  • локальные изменения через test и transform;
  • точечное извлечение через reach;
  • контекстные зависимости через ref.

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