Связь с Yup и React Hook Form

Связка Yup и React Hook Form строится вокруг концепции внешней схемной валидации, где React Hook Form отвечает за управление состоянием формы, а Yup — за декларативное описание правил проверки данных. Связующим слоем выступает YupResolver из пакета @hookform/resolvers, который преобразует Yup-валидацию в формат, понимаемый React Hook Form.

Ключевая идея заключается в том, что React Hook Form не выполняет синхронную проверку значений сам по себе в момент изменения поля. Вместо этого он делегирует проверку резолверу, а тот уже вызывает Yup-схему и возвращает структурированный результат: валидные данные либо набор ошибок.

Механизм работы YupResolver внутри цикла валидации

При каждом триггере валидации (submit, change, blur — в зависимости от режима mode) React Hook Form передаёт текущие значения формы в функцию резолвера. YupResolver выполняет следующие шаги:

  1. Принимает объект значений формы.

  2. Передаёт его в schema.validate или schema.validateSync (в зависимости от конфигурации).

  3. Обрабатывает результат:

    • при успехе формирует объект { values, errors: {} }
    • при ошибке преобразует Yup ValidationError в структуру ошибок RHF

Важный аспект: Yup возвращает ошибки в виде дерева исключений, тогда как React Hook Form ожидает плоскую или вложенную структуру с привязкой к именам полей.

Базовая интеграция YupResolver в React Hook Form

Основной способ подключения строится через useForm:

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

Сначала определяется схема:

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

Далее схема передаётся в резолвер:

const {
  register,
  handleSubmit,
  formState: { errors }
} = useForm({
  resolver: yupResolver(schema)
});

На этом уровне YupResolver становится промежуточным слоем между UI и правилами валидации.

Формат данных и преобразование ошибок

Yup генерирует ошибки в формате ValidationError, который содержит:

  • path — путь к полю (например, user.email)
  • message — текст ошибки
  • inner — массив вложенных ошибок при object().shape

YupResolver преобразует это в структуру React Hook Form:

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

Для вложенных объектов:

{
  user: {
    email: {
      message: "Required"
    }
  }
}

Это преобразование критично для корректного отображения ошибок в UI без дополнительной нормализации со стороны разработчика.

Сложные схемы и вложенные структуры

Yup особенно полезен при работе с вложенными объектами и массивами:

const schema = yup.object({
  user: yup.object({
    profile: yup.object({
      firstName: yup.string().required(),
      lastName: yup.string().required()
    })
  })
});

React Hook Form при этом использует точечную нотацию:

register("user.profile.firstName");

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

Работа с массивами и динамическими полями

При использовании yup.array() появляется необходимость синхронизации динамических форм:

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

React Hook Form управляет такими структурами через useFieldArray, а YupResolver обеспечивает валидацию каждого элемента массива.

Особенность заключается в том, что ошибки возвращаются с индексной адресацией:

users[0].name
users[2].age

Это позволяет точно локализовать ошибки в UI-списках.

Режимы валидации и влияние на YupResolver

React Hook Form поддерживает несколько режимов:

  • onSubmit
  • onChange
  • onBlur
  • all

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

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

  • отключение abortEarly: false только при необходимости
  • использование validateSync в простых формах
  • мемоизация схемы

Асинхронная валидация и YupResolver

Yup поддерживает асинхронные проверки через test:

yup.string().test(
  "checkEmail",
  "Email already exists",
  async (value) => {
    return await api.checkEmail(value);
  }
);

YupResolver автоматически поддерживает async-схемы, возвращая Promise. React Hook Form в этом случае переходит в режим ожидания результата валидации.

Это важно для сценариев:

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

Кастомизация ошибок и нормализация сообщений

Yup позволяет централизованно управлять сообщениями:

yup.string().required("Поле обязательно")

Однако в крупных приложениях часто используется трансляция ошибок через setLocale:

yup.setLocale({
  mixed: {
    required: "Обязательное поле"
  }
});

YupResolver при этом не вмешивается в текст сообщений, а только транспортирует их в RHF.

Типизация TypeScript и YupResolver

В TypeScript связка приобретает дополнительную строгость:

import { InferType } from "yup";

const schema = yup.object({
  email: yup.string().email().required(),
  age: yup.number().required()
});

type FormData = InferType<typeof schema>;

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

useForm<FormData>({
  resolver: yupResolver(schema)
});

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

Производительность и внутренние издержки

YupResolver добавляет слой абстракции, который влияет на производительность в зависимости от сложности схемы:

Основные факторы нагрузки:

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

Оптимизационные подходы:

  • разделение схем на подмодули
  • использование when() вместо сложных условных конструкций в JS
  • минимизация пересоздания схемы в рендерах

React Hook Form сам по себе минимизирует перерендеры, но YupResolver выполняется при каждой валидации, поэтому схема должна быть стабильной.

Условная валидация через Yup и влияние на Resolver

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

yup.object({
  isCompany: yup.boolean(),
  companyName: yup.string().when("isCompany", {
    is: true,
    then: (schema) => schema.required()
  })
});

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

Поведение при частичных данных

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

  • проверяет только присутствующие поля
  • применяет default значения Yup, если они заданы
  • возвращает ошибки только для активных ключей
yup.string().default("guest")

Обработка ошибок в UI-слое

Ошибки, возвращаемые через formState.errors, используются напрямую:

{errors.email?.message}

YupResolver гарантирует, что структура ошибок:

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

Сравнение с альтернативными резолверами

В экосистеме React Hook Form существуют альтернативы:

  • Zod Resolver
  • Joi Resolver
  • Valibot Resolver

YupResolver отличается:

  • зрелой экосистемой Yup
  • богатым API трансформаций
  • широкой поддержкой legacy-кода

При этом Yup менее строг в типизации по сравнению с Zod, что компенсируется TypeScript-обвязкой через InferType.

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

Если Yup-схема некорректна или выбрасывает исключение вне ValidationError, YupResolver:

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

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

Согласование состояния формы и схемы

React Hook Form хранит состояние независимо от Yup. Это означает:

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

YupResolver выполняет роль синхронизатора между двумя независимыми системами: state management и validation engine.