Зависимости между полями

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

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

Ключевой принцип:

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

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


Ссылка на другие поля через ref

Один из основных механизмов — использование ref.

import * as Yup from 'yup';

const schema = Yup.object({
  password: Yup.string().required('Обязательное поле'),
  confirmPassword: Yup.string()
    .oneOf([Yup.ref('password')], 'Пароли не совпадают')
    .required('Подтвердите пароль'),
});

Здесь:

  • Yup.ref('password') создаёт ссылку на другое поле объекта
  • oneOf сравнивает текущее значение с целевым
  • при изменении password автоматически пересчитывается валидность confirmPassword

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


Условная логика через when

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

const schema = Yup.object({
  isCompany: Yup.boolean(),
  companyName: Yup.string().when('isCompany', {
    is: true,
    then: schema => schema.required('Укажите название компании'),
    otherwise: schema => schema.notRequired(),
  }),
});

Механика работы

  • when('isCompany') подписывает поле на изменения другого поля
  • is задаёт условие
  • then применяется при истинном условии
  • otherwise — при ложном

Таким образом, схема становится динамической.


Зависимость от нескольких полей

when поддерживает массив зависимостей:

const schema = Yup.object({
  age: Yup.number(),
  hasConsent: Yup.boolean(),
  guardianConsent: Yup.boolean().when(['age', 'hasConsent'], {
    is: (age, hasConsent) => age < 18 && !hasConsent,
    then: schema => schema.oneOf([true], 'Требуется согласие опекуна'),
  }),
});

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


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

Когда логика выходит за рамки when, применяется test, где доступен весь объект:

const schema = Yup.object({
  startDate: Yup.date(),
  endDate: Yup.date().test(
    'date-order',
    'Дата окончания должна быть позже начала',
    function (value) {
      const { startDate } = this.parent;
      if (!startDate || !value) return true;
      return value > startDate;
    }
  ),
});

Важные элементы:

  • this.parent — доступ к объекту текущей формы
  • value — текущее значение поля
  • возвращаемое значение определяет валидность

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


Контекст объекта и this

Внутри кастомных валидаторов Yup предоставляет контекст:

  • this.parent — родительский объект
  • this.path — путь текущего поля
  • this.createError — создание кастомной ошибки

Пример расширенной проверки:

Yup.string().test(
  'conditional-length',
  'Слишком короткое значение',
  function (value) {
    const { type } = this.parent;

    if (type === 'strict') {
      return value && value.length >= 10;
    }

    return true;
  }
);

Здесь поведение поля зависит от другого поля type.


Вложенные объекты и глубокие зависимости

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

const schema = Yup.object({
  user: Yup.object({
    role: Yup.string(),
    permissions: Yup.object({
      canEdit: Yup.boolean().when('role', {
        is: 'admin',
        then: schema => schema.oneOf([true]),
      }),
    }),
  }),
});

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

  • путь 'role' может относиться к ближайшему объекту
  • для глубоких ссылок используется полный путь user.role

Динамическая схема через lazy

Когда структура данных зависит от значений, используется lazy:

const schema = Yup.lazy(value => {
  if (value?.type === 'email') {
    return Yup.object({
      type: Yup.string(),
      email: Yup.string().email().required(),
    });
  }

  return Yup.object({
    type: Yup.string(),
    phone: Yup.string().required(),
  });
});

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


Зависимость через concat и композицию схем

Иногда зависимости строятся не через условия, а через объединение схем:

const base = Yup.object({
  type: Yup.string().required(),
});

const extended = Yup.object({
  extra: Yup.string().when('type', {
    is: 'advanced',
    then: schema => schema.required(),
  }),
});

const schema = base.concat(extended);

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


Частые паттерны зависимостей

Подтверждение значений

  • пароль / подтверждение пароля
  • email / повтор email

Реализуется через ref + oneOf.


Диапазоны

  • дата начала / дата окончания
  • минимальное / максимальное значение

Реализуется через test или when.


Условная обязательность

  • поле обязательно только при определённом выборе

Реализуется через when.


Взаимное исключение

Yup.object({
  phone: Yup.string().when('email', {
    is: value => !value,
    then: schema => schema.required(),
  }),
  email: Yup.string().when('phone', {
    is: value => !value,
    then: schema => schema.required(),
  }),
});

Оба поля зависят друг от друга, образуя взаимосвязанный цикл условий.


Особенности поведения зависимостей

При построении схем с зависимостями важно учитывать:

  • изменения одного поля могут триггерить пересчёт других
  • порядок объявления не влияет на работу when и ref
  • циклические зависимости требуют аккуратной логики в test
  • lazy полностью пересобирает схему при изменении входных данных

Комбинирование подходов

В реальных схемах часто сочетаются разные механизмы:

  • ref для простых сравнений
  • when для декларативных условий
  • test для сложной логики
  • lazy для динамической структуры

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