Динамические схемы

Природа динамических схем в связке Yup и YupResolver

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

При использовании react-hook-form через YupResolver, динамичность схемы становится не просто возможностью, а архитектурной задачей: resolver пересобирает правила валидации при изменении условий, обеспечивая актуальность проверок без ручного управления логикой валидации.

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


Фабричный подход к построению схем

Базовый паттерн динамических схем — фабрика схемы. Схема представляется как функция, возвращающая Yup-объект в зависимости от входных параметров.

import * as yup from "yup";

const createSchema = (mode) => {
  return yup.object({
    email: yup.string().email().required(),

    password: mode === "register"
      ? yup.string().min(8).required()
      : yup.string().notRequired(),

    confirmPassword: mode === "register"
      ? yup.string()
          .oneOf([yup.ref("password")], "Пароли должны совпадать")
          .required()
      : yup.string().notRequired(),
  });
};

Такой подход позволяет:

  • изменять обязательность полей
  • включать/исключать правила
  • адаптировать структуру формы под контекст

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


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

В связке с react-hook-form схема передаётся через resolver, и любое изменение схемы должно приводить к обновлению формы.

import { useMemo } from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";

const formMode = "register";

const schema = useMemo(() => createSchema(formMode), [formMode]);

const form = useForm({
  resolver: yupResolver(schema),
  mode: "onChange",
});

Использование useMemo критично: без мемоизации каждая перерисовка компонента создаёт новую схему, что приводит к:

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

Условная логика внутри схем

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

const schema = yup.object({
  hasCompany: yup.boolean(),

  companyName: yup.string().when("hasCompany", {
    is: true,
    then: (schema) => schema.required("Название компании обязательно"),
    otherwise: (schema) => schema.notRequired(),
  }),
});

Такая конструкция переносит логику из UI в слой валидации, уменьшая связность компонентов.


Зависимость схемы от внешнего состояния

Помимо значений формы, схема может зависеть от внешних факторов:

  • роль пользователя
  • конфигурация сервера
  • feature flags
  • язык или регион
  • тип операции (создание / редактирование)
const buildSchema = ({ isAdmin, country }) =>
  yup.object({
    username: yup.string().required(),

    taxId: country === "KZ"
      ? yup.string().length(12)
      : yup.string().notRequired(),

    permissions: isAdmin
      ? yup.array().min(1)
      : yup.array().strip(),
  });

Использование strip() позволяет исключать поля из результирующего объекта, что важно при серверной отправке данных.


Пересборка схемы при изменении формы

При динамических схемах ключевой архитектурной проблемой становится момент пересоздания resolver. React Hook Form не всегда ожидает смену схемы в рантайме, поэтому требуется контролируемое обновление формы.

const schema = useMemo(
  () => buildSchema({ isAdmin, country }),
  [isAdmin, country]
);

const form = useForm({
  resolver: yupResolver(schema),
});

При изменении зависимостей возможны два подхода:

  • полная пересоздача формы через reset
  • сохранение состояния с повторной валидацией

Частично динамические схемы

В более сложных системах схема не пересоздаётся целиком, а изменяется частично через композицию.

const baseSchema = yup.object({
  email: yup.string().email().required(),
});

const adminExtension = yup.object({
  role: yup.string().required(),
  permissions: yup.array().min(1),
});

const schema = isAdmin
  ? baseSchema.concat(adminExtension)
  : baseSchema;

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


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

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

const schema = yup.object({
  items: yup.array().of(
    yup.object({
      type: yup.string().required(),

      value: yup.string().when("type", {
        is: "text",
        then: (s) => s.required(),
        otherwise: (s) => s.notRequired(),
      }),
    })
  ),
});

Такие схемы часто применяются в:

  • конструкторе форм
  • CMS-интерфейсах
  • настройках интеграций
  • конфигурационных панелях

Производительность динамических схем

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

Основные оптимизации:

1. Мемоизация схемы

  • предотвращает лишние пересоздания объектов Yup

2. Разделение схем

  • разбиение на базовые и расширяемые части

3. Ленивая сборка

  • генерация схемы только при изменении критических зависимостей

4. Минимизация вложенности when

  • глубокие зависимости ухудшают читаемость и увеличивают стоимость вычислений

Типизация динамических схем

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

type FormValues = {
  mode: "basic" | "advanced";
  email: string;
  password?: string;
};

const createSchema = (mode: FormValues["mode"]) =>
  yup.object({
    email: yup.string().required(),
    password: mode === "advanced"
      ? yup.string().required()
      : yup.string().notRequired(),
  });

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


Композиция условий и бизнес-логики

В сложных приложениях динамические схемы становятся отражением бизнес-правил. При этом важно отделять:

  • чистую валидацию (Yup)
  • бизнес-условия (сервисный слой)
  • UI-логику (React)

Нарушение этого разделения приводит к появлению «монолитных схем», которые трудно тестировать и поддерживать.


Ошибки проектирования динамических схем

Типовые проблемы:

  • пересоздание схемы на каждый рендер
  • дублирование условий в UI и Yup
  • чрезмерное использование when
  • отсутствие централизованной фабрики схем
  • смешивание бизнес-логики и валидации
  • отсутствие стабильных ключей формы при динамическом наборе полей