Валидация части формы

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

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

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

Резолвер выступает адаптером между схемой Yup и системой управления формой. При каждом событии изменения поля React Hook Form передаёт текущее состояние значений в resolver, но дальнейшая стратегия зависит от режима валидации:

  • onChange — проверка запускается при каждом изменении поля
  • onBlur — проверка при потере фокуса
  • onSubmit — проверка всей формы
  • onTouched — гибридный режим

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

YupResolver не выполняет ручное разбиение схемы, однако Yup предоставляет инструменты, позволяющие валидировать отдельные пути, что используется внутри React Hook Form при вызове методов вроде trigger('fieldName').

Валидация конкретного поля через trigger

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

trigger("email")

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

При использовании схемы:

import * as yup from "yup";

const schema = yup.object({
  email: yup.string().email().required(),
  password: yup.string().min(8).required(),
});

и резолвера:

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

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

вызов trigger("email") приводит к проверке только email-ветки, не затрагивая password.

Поведение Yup при частичной проверке

Внутри Yup схема остаётся целостной, но библиотека поддерживает проверку по пути:

  • schema.validateAt(path, values)
  • schema.reach(schema, path)

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

YupResolver использует эти механизмы косвенно, когда React Hook Form запрашивает проверку отдельного поля. Это обеспечивает высокую производительность на больших формах.

Структура ошибок при частичной валидации

Ошибки формируются в виде объекта:

{
  email: {
    type: "email",
    message: "Invalid email"
  }
}

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

Это важно для динамических форм, где не все поля активны одновременно.

Связь частичной валидации и режима re-validation

Режимы reValidateMode и mode в React Hook Form напрямую влияют на частичную проверку:

  • onChange приводит к постоянному обновлению состояния ошибок по отдельным полям
  • onBlur изолирует проверку до момента выхода из поля
  • onSubmit откладывает всю проверку до финального действия

При этом YupResolver остаётся пассивным адаптером и не управляет логикой вызова, а только интерпретирует результат Yup.

Динамические формы и частичная валидация

В сложных сценариях, например при использовании массивов полей:

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

частичная валидация становится критически важной. При изменении одного элемента массива React Hook Form вызывает проверку только конкретного индекса:

trigger("users[2].name");

YupResolver корректно обрабатывает такие пути благодаря поддержке вложенных структур Yup.

Условная логика и частичная проверка

Частичная валидация часто сочетается с условной логикой схемы:

const schema = yup.object({
  hasPhone: yup.boolean(),
  phone: yup.string().when("hasPhone", {
    is: true,
    then: (schema) => schema.required(),
    otherwise: (schema) => schema.notRequired(),
  }),
});

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

Оптимизация частичной валидации

На уровне больших форм основная нагрузка возникает при:

  • частых onChange событиях
  • глубоко вложенных схемах
  • массивах объектов
  • сложных when зависимостях

Оптимизация достигается комбинацией стратегий:

  • использование mode: "onBlur" вместо onChange
  • точечные вызовы trigger
  • разделение формы на логические блоки
  • мемоизация схемы Yup
  • избежание пересоздания resolver на каждом рендере

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

Работа с контекстом в частичной валидации

Yup поддерживает передачу контекста:

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

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

Синхронизация состояния ошибок

React Hook Form хранит ошибки в кэше состояния формы. При частичной валидации происходит:

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

Это предотвращает «мерцание» ошибок в интерфейсе и делает UX стабильным даже при сложной логике проверки.

Глубокие пути и изолированная валидация

Вложенные структуры требуют корректной адресации:

  • profile.address.city
  • items[3].price
  • settings.notifications.email

YupResolver сохраняет соответствие между путями React Hook Form и внутренними путями Yup, обеспечивая точечную проверку без необходимости валидировать всю схему.

Практика использования частичной валидации в сложных сценариях

В формах с пошаговой логикой (wizard-формы) частичная валидация становится основным механизмом:

  • каждый шаг содержит подмножество полей
  • проверка выполняется только для активного шага
  • данные остальных шагов игнорируются до перехода

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

Поведение при ошибках асинхронной валидации

Если Yup содержит асинхронные проверки (например, через test с API-запросом), частичная валидация выполняется аналогично, но может приводить к конкурентным состояниям:

  • несколько trigger одновременно
  • устаревшие результаты проверки
  • перезапись ошибок

React Hook Form решает это через идентификаторы запросов и последовательное обновление состояния.

Влияние частичной валидации на производительность

Основное преимущество частичной проверки заключается в снижении количества операций:

  • уменьшается число вызовов Yup.validate
  • сокращается обход схемы
  • уменьшается количество обновлений состояния формы

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

Взаимодействие с defaultValues и reset

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

  • сброс значений
  • сброс ошибок
  • повторная инициализация resolver-контекста

После этого частичная валидация начинается с чистого состояния, и первые trigger-вызовы формируют новый набор ошибок независимо от предыдущих данных.