Ref - ссылки на другие поля

Механизм ссылок на другие поля в схемах валидации позволяет строить зависимые правила, где значение одного поля участвует в проверке другого. В экосистеме Yup это реализуется через Yup.ref, который формирует «указатель» на значение поля внутри текущего объекта схемы. В связке с YupResolver этот подход становится ключевым инструментом для построения согласованных форм с перекрёстной валидацией.

Природа Yup.ref и принцип работы

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

Суть механизма:

  • схема описывает структуру данных
  • ref указывает, какое поле использовать как источник значения
  • YupResolver передаёт в Yup текущие значения формы
  • во время проверки Yup «разворачивает» ссылку и подставляет актуальное значение

Базовая форма использования:

Yup.ref('fieldName')

Такой вызов означает: «возьми значение поля fieldName из текущего объекта данных».

Сравнение полей внутри схемы

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

Пример сравнения строк:

import * as Yup from 'yup';

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

Здесь confirmPassword зависит от значения password. YupResolver передаёт объект формы целиком, и Yup проверяет соответствие в момент валидации.

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

Ссылки работают не только со строками, но и с числами:

const schema = Yup.object({
  min: Yup.number().required(),
  max: Yup.number()
    .min(Yup.ref('min'), 'Максимум должен быть больше минимума')
});

Такая схема гарантирует логическую согласованность диапазона. Валидация происходит динамически, поэтому изменение min сразу влияет на поведение max.

Работа с датами и временными диапазонами

Один из наиболее практичных сценариев — контроль дат.

const schema = Yup.object({
  startDate: Yup.date().required(),
  endDate: Yup.date()
    .min(Yup.ref('startDate'), 'Дата окончания не может быть раньше начала')
});

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

Применение ref внутри массивов и вложенных объектов

Сложные формы часто содержат структуры:

const schema = Yup.object({
  items: Yup.array().of(
    Yup.object({
      price: Yup.number().required(),
      discountPrice: Yup.number()
        .max(Yup.ref('price'), 'Скидка не может превышать цену')
    })
  )
});

Здесь ref работает в локальном контексте объекта массива. Это означает, что ссылка всегда относится к полям текущего элемента, а не к внешней структуре.

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

Контекст выполнения и разрешение ссылок

YupResolver передаёт данные формы в Yup как единый объект. В момент валидации:

  1. формируется snapshot значений формы
  2. Yup проходит по схеме
  3. ref разрешается относительно текущего уровня объекта
  4. результат подставляется в выражение проверки

Это означает, что Yup.ref не является реактивной ссылкой в стиле state management. Это вычисляемое значение, актуальное на момент вызова validate.

Использование ref в условной логике 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 использует внутренний механизм ссылок, аналогичный ref. Более явная форма:

Yup.string().when(Yup.ref('isCompany'), {
  is: true,
  then: (schema) => schema.required()
});

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

Ограничения и особенности поведения

Несмотря на гибкость, механизм ссылок имеет ряд особенностей:

  • ref не отслеживает изменения в реальном времени вне валидации
  • циклические ссылки могут приводить к некорректной логике
  • значения undefined могут влиять на сравнение
  • преобразования (transform) выполняются до разрешения ссылки

Пример потенциальной проблемы:

Yup.number().min(Yup.ref('minValue'))

Если minValue отсутствует, результат сравнения может стать неожиданным. В таких случаях требуется явная обработка:

Yup.number().when('minValue', (minValue, schema) =>
  minValue !== undefined
    ? schema.min(minValue)
    : schema
)

Взаимодействие ref и default values в YupResolver

При использовании YupResolver в связке с формами (например, React Hook Form) важно учитывать порядок инициализации:

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

Это означает, что если поле не задано в defaultValues, ref может ссылаться на undefined, что изменяет поведение условий.

Типовые сценарии применения

На практике Yup.ref чаще всего используется в следующих случаях:

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

Yup.string().oneOf([Yup.ref('email')])

Диапазоны:

Yup.number().max(Yup.ref('budget'))

Зависимые поля:

Yup.string().when('mode', (mode, schema) =>
  mode === 'advanced' ? schema.required() : schema
)

Кросс-валидация объектов:

Yup.object({
  from: Yup.date(),
  to: Yup.date().min(Yup.ref('from'))
})

Поведение внутри YupResolver при ошибках

Когда схема содержит ref, YupResolver обрабатывает ошибки так же, как и обычные проверки:

  • ошибка привязывается к полю-источнику проверки
  • сообщение формируется на уровне условия (min, oneOf, required)
  • зависимости не дублируют ошибки между полями

Например, если confirmPassword не совпадает с password, ошибка будет принадлежать только confirmPassword.

Глубокие ссылки и строковая нотация

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

Yup.ref('user.password')

Это особенно полезно для структурированных объектов:

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

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

Поведение при трансформациях

Если поле содержит transform, порядок выполнения становится критичным:

Yup.number()
  .transform((value) => Number(value))
  .min(Yup.ref('minValue'))

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

Совместимость с кастомными тестами

Yup.ref может использоваться внутри .test():

Yup.string().test(
  'match-check',
  'Значения не совпадают',
  function (value) {
    return value === this.resolve(Yup.ref('otherField'))
  }
)

Здесь this.resolve — внутренний механизм Yup, который разворачивает ссылку в актуальное значение.

Практические нюансы при масштабировании форм

В крупных формах с множеством зависимостей ref становится инструментом связывания логики:

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

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

Особенности работы с динамическими ключами

Если имя поля формируется динамически, ref может стать менее предсказуемым:

Yup.ref(`fields.${index}.value`)

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

Поведение при отсутствии значения

Если целевое поле отсутствует, Yup применяет стандартные правила сравнения:

  • undefined считается отсутствующим значением
  • сравнения могут возвращать false
  • некоторые методы (oneOf, equals) могут вести себя по-разному в зависимости от типа данных

Это требует явного контроля обязательности полей при проектировании схем.

Использование ref как основы декларативной логики

Механизм ссылок превращает схему Yup в граф зависимостей между полями. В сочетании с YupResolver это позволяет:

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

При этом вся логика остаётся синхронной и детерминированной, что упрощает отладку и тестирование форм.