Refs и ссылки между полями

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


Основной механизм yup.ref

Функция yup.ref() создаёт ссылку на другое поле в пределах текущего объекта схемы. Это не прямое обращение к значению в момент объявления, а динамическая привязка, которая разрешается во время валидации.

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

Простейший пример:

import * as yup fr om 'yup';

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

Здесь confirmPassword сравнивается с password через yup.ref('password'). При изменении значения password в форме результат валидации автоматически изменится без изменения самой схемы.


Поведение refs в контексте объекта

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

const schema = yup.object({
  user: yup.object({
    password: yup.string().required(),
    confirm: yup
      .string()
      .oneOf([yup.ref('user.password')], 'Ошибка совпадения')
  })
});

Здесь используется путь user.password, который интерпретируется как доступ к вложенному значению через точечную нотацию.


Использование ref в числовых сравнениях

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

const schema = yup.object({
  min: yup.number().required(),
  max: yup
    .number()
    .required()
    .min(yup.ref('min'), 'max должен быть больше min')
});

yup.ref('min') подставляет текущее значение поля min в момент проверки поля max.

Важно, что порядок объявления полей не влияет на корректность работы — Yup разрешает ссылки во время валидации всего объекта.


Динамические условия с when и refs

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

const schema = yup.object({
  type: yup.string().required(),
  value: yup.string().when('type', {
    is: 'email',
    then: schema => schema.email(),
    otherwise: schema => schema.min(3)
  })
});

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


Доступ к значению через this.resolve

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

const schema = yup.object({
  start: yup.date(),
  end: yup.date().test('check-range', function (value) {
    const start = this.resolve(yup.ref('start'));

    if (!start || !value) return true;

    return value >= start;
  })
});

Метод this.resolve позволяет получить актуальное значение поля, на которое указывает ref, в момент выполнения проверки.


Использование refs в test

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

const schema = yup.object({
  password: yup.string(),
  confirm: yup.string().test(
    'match',
    'Пароли не совпадают',
    function (value) {
      return value === this.resolve(yup.ref('password'));
    }
  )
});

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


Работа с массивами и ссылками

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

const schema = yup.object({
  items: yup.array().of(
    yup.object({
      min: yup.number(),
      max: yup.number().min(yup.ref('min'))
    })
  )
});

Внутри of() ссылка min относится к текущему объекту элемента массива, а не к внешнему уровню.


Абсолютные и относительные пути в ref

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

  • field — текущий уровень объекта
  • parent.field — родительский уровень
  • nested.field — вложенная структура
yup.ref('settings.lim it')
yup.ref('$contextValue')

Также возможны ссылки на контекст, передаваемый при валидации:

schema.validate(data, { context: { role: 'admin' } });

И доступ:

yup.ref('$role')

Повторное использование ссылок

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

const passwordRef = yup.ref('password');

const schema = yup.object({
  password: yup.string(),
  confirm: yup.string().oneOf([passwordRef]),
  oldPassword: yup.string().notOneOf([passwordRef])
});

Это уменьшает дублирование и упрощает поддержку сложных схем.


Особенности разрешения значений

refs разрешаются в момент валидации, а не в момент создания схемы. Это означает:

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

Однако есть важное ограничение: ссылки не работают вне контекста Yup-объекта. Попытка использовать ref как обычную переменную приведёт к отсутствию значения.


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

В реальных формах часто возникает цепочка зависимостей:

const schema = yup.object({
  minAge: yup.number(),
  maxAge: yup
    .number()
    .min(yup.ref('minAge'))
    .when('minAge', (minAge, schema) => {
      if (minAge > 18) {
        return schema.max(100);
      }
      return schema.max(50);
    })
});

Здесь одновременно используются ref и when, создавая многоуровневую зависимость между значениями.


Типичные ошибки при работе с refs

Одной из частых проблем является попытка использовать несуществующий путь:

yup.ref('user.password') // если user отсутствует в структуре

В этом случае Yup не выбрасывает ошибку сразу, но значение становится undefined, что приводит к некорректной валидации.

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


Комбинирование refs с преобразованием данных

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

yup.string().transform((value) => value.trim())
  .oneOf([yup.ref('otherField')])

Важно учитывать, что ref работает уже после трансформации значений, поэтому сравнение происходит на итоговых данных.


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

При сложных структурах данных ссылки могут становиться длинными:

yup.ref('settings.notifications.email.enabled')

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


Поведение refs при частичной валидации

При валидации отдельных полей (validateAt) ссылки всё равно разрешаются в рамках всего объекта. Это означает, что Yup требует доступ к полной структуре данных, даже если проверяется одно поле.

schema.validateAt('confirmPassword', data)

Если password отсутствует в data, результат ссылки будет undefined.