Динамические схемы на основе данных

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

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

Ключевая особенность заключается в том, что схема становится функцией от состояния:

import * as Yup from 'yup';

const createSchema = (config) => {
  return Yup.object().shape({
    username: Yup.string().required('Обязательное поле'),

    email: config.requireEmail
      ? Yup.string().email('Некорректный email').required('Email обязателен')
      : Yup.string().email('Некорректный email').notRequired(),
  });
};

Здесь структура валидации зависит от config.requireEmail, что позволяет гибко управлять поведением формы.

Использование контекста для динамики

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

const schema = Yup.object().shape({
  password: Yup.string().required(),
  confirmPassword: Yup.string().oneOf(
    [Yup.ref('password')],
    'Пароли должны совпадать'
  ),
});

schema.validate(data, {
  context: { minPasswordStrength: 3 }
});

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

Условная валидация через when

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

const schema = Yup.object().shape({
  hasPhone: Yup.boolean(),

  phone: Yup.string().when('hasPhone', {
    is: true,
    then: (schema) => schema.required('Укажите номер телефона'),
    otherwise: (schema) => schema.notRequired(),
  }),
});

Здесь поле phone становится обязательным только при включённом флаге hasPhone.

Можно усложнять условия, используя несколько зависимостей:

const schema = Yup.object().shape({
  role: Yup.string(),
  accessCode: Yup.string().when('role', {
    is: (val) => val === 'admin' || val === 'moderator',
    then: (schema) => schema.required('Код доступа обязателен'),
    otherwise: (schema) => schema.strip(),
  }),
});

Метод strip() удаляет поле из результата валидации, что полезно при работе с условными формами.

Динамическое построение структуры объекта

В сложных формах структура объекта может зависеть от внешних данных: списка полей с сервера, конфигурации UI или пользовательских настроек.

const buildSchemaFromFields = (fields) => {
  const shape = {};

  fields.forEach((field) => {
    if (field.type === 'text') {
      shape[field.name] = Yup.string();
    }

    if (field.required) {
      shape[field.name] = shape[field.name].required('Обязательное поле');
    }

    if (field.type === 'number') {
      shape[field.name] = Yup.number();
    }
  });

  return Yup.object().shape(shape);
};

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

Использование 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();
});

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

Динамические массивы и вложенные структуры

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

const schema = Yup.object().shape({
  users: Yup.array().of(
    Yup.object().shape({
      name: Yup.string().required(),
      age: Yup.number().min(18),
    })
  ),
});

Если структура элементов массива зависит от внешних условий, можно комбинировать when и lazy:

const userSchema = Yup.lazy((value) => {
  if (value?.type === 'admin') {
    return Yup.object({
      permissions: Yup.array().of(Yup.string()).required(),
    });
  }

  return Yup.object({
    permissions: Yup.array().notRequired(),
  });
});

Комбинирование схем через функции-фабрики

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

const createPasswordSchema = ({ minLength = 8 }) =>
  Yup.string()
    .min(minLength, `Минимум ${minLength} символов`)
    .required('Пароль обязателен');

const schema = Yup.object().shape({
  password: createPasswordSchema({ minLength: 12 }),
});

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

Валидация на основе внешних данных

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

const createSchema = (settings) =>
  Yup.object().shape({
    age: Yup.number()
      .min(settings.minAge)
      .max(settings.maxAge)
      .required(),
  });

Если настройки обновляются динамически, схема может пересоздаваться при каждом изменении состояния приложения.

Сложные зависимости между полями

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

const schema = Yup.object().shape({
  country: Yup.string(),

  region: Yup.string().when('country', {
    is: 'Russia',
    then: (schema) => schema.required(),
    otherwise: (schema) => schema.notRequired(),
  }),

  city: Yup.string().when(['country', 'region'], {
    is: (country, region) => country === 'Russia' && region === 'Moscow',
    then: (schema) => schema.required('Укажите город'),
  }),
});

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

Интеграция динамических схем с формами

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

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

Ограничения динамического подхода

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

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

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

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