Vest resolver

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

Vest реализует подход, вдохновлённый тестовыми фреймворками: правила валидации оформляются как набор проверок, группируемых в «сессии». Resolver выступает мостом между результатом выполнения этих проверок и форматом ошибок, который ожидает React Hook Form.


Архитектурная модель Vest resolver

Vest resolver выполняет три ключевые функции:

1. Запуск сессии валидации Vest Передача данных формы в функцию vest-схемы и запуск набора правил.

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

3. Преобразование в формат React Hook Form Результат конвертируется в объект вида:

{
  values: Record<string, any>,
  errors: Record<string, { type: string; message: string }>
}

Этот формат строго соответствует контракту резолверов React Hook Form.


Установка и подключение зависимостей

Для использования Vest resolver требуется базовая связка:

npm install react-hook-form vest @hookform/resolvers

Или:

yarn add react-hook-form vest @hookform/resolvers

Пакет @hookform/resolvers предоставляет адаптеры для различных библиотек валидации, включая Yup, Zod, Joi и Vest.


Принцип работы Vest схемы

Vest строится вокруг функции vest.create, которая определяет набор правил:

import { create, test, enforce } from 'vest';

const loginSuite = create((data = {}) => {
  test('email', 'Email обязателен', () => {
    enforce(data.email).isNotEmpty();
  });

  test('email', 'Некорректный email', () => {
    enforce(data.email).matches(/.+@.+\..+/);
  });

  test('password', 'Пароль слишком короткий', () => {
    enforce(data.password).longerThan(6);
  });
});

Каждый test привязывается к конкретному полю формы. Это ключевой момент для дальнейшего маппинга в ошибки React Hook Form.


Интеграция Vest resolver с React Hook Form

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

import { useForm } from 'react-hook-form';
import { vestResolver } from '@hookform/resolvers/vest';
import { loginSuite } from './validation';

const {
  register,
  handleSubmit,
  formState: { errors },
} = useForm({
  resolver: vestResolver(loginSuite),
});

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


Структура ошибок и маппинг полей

Vest формирует ошибки в виде массива тестов, но React Hook Form ожидает объект с ключами полей.

Пример внутреннего результата Vest:

{
  "valid": false,
  "tests": [
    {
      "field": "email",
      "message": "Email обязателен"
    },
    {
      "field": "password",
      "message": "Пароль слишком короткий"
    }
  ]
}

После обработки resolver преобразует это в:

{
  email: { type: 'manual', message: 'Email обязателен' },
  password: { type: 'manual', message: 'Пароль слишком короткий' }
}

Работа с несколькими ошибками одного поля

Vest поддерживает множественные проверки одного поля. Resolver применяет стратегию:

  • либо возвращается первая ошибка,
  • либо формируется агрегированный список (в зависимости от конфигурации).

Пример:

test('email', 'Email обязателен', ...)
test('email', 'Некорректный формат', ...)

В React Hook Form по умолчанию будет отображена последняя или приоритетная ошибка.


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

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

test('username', 'Имя уже занято', async () => {
  await delay(300);
  enforce(await isUsernameTaken(data.username)).isFalsy();
});

Resolver корректно обрабатывает Promise-результаты и дожидается завершения всех тестов перед возвратом результата в React Hook Form.


Оптимизация и кэширование результатов

Vest имеет встроенный механизм оптимизации:

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

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


Поведение при режиме validate / submit

В React Hook Form различаются режимы валидации:

  • onSubmit — проверка при отправке формы
  • onChange — проверка при изменении поля
  • onBlur — проверка при потере фокуса

Vest resolver вызывается в каждом из режимов одинаково, однако Vest внутри может переиспользовать результаты прошлых сессий, снижая нагрузку.


Типизация TypeScript

Для строгой типизации рекомендуется явно описывать структуру формы:

type FormValues = {
  email: string;
  password: string;
};

const loginSuite = create<FormValues>((data) => {
  test('email', 'Email обязателен', () => {
    enforce(data.email).isNotEmpty();
  });
});

React Hook Form автоматически выводит типы через useForm<FormValues>(), обеспечивая согласованность данных между формой и схемой валидации.


Обработка кастомных ошибок

Vest позволяет формировать произвольные ошибки:

test('password', 'Слишком простой пароль', () => {
  if (data.password === '123456') {
    fail('Слишком распространённый пароль');
  }
});

Resolver интерпретирует такие ошибки как стандартные и добавляет их в объект errors.


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

Vest работает по принципу полного прогона сессии. Это означает:

  • даже при первой ошибке остальные тесты продолжают выполняться
  • результат содержит полный список проблем
  • React Hook Form получает полный объект ошибок за один цикл

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


Сравнение с другими резолверами

В экосистеме @hookform/resolvers существуют альтернативы:

  • Yup resolver (схемный подход)
  • Zod resolver (строгая типизация)
  • Joi resolver (серверно-ориентированный стиль)
  • Vest resolver (тестовый декларативный стиль)

Vest выделяется тем, что:

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

Ошибки интеграции и их причины

Типичные проблемы при использовании Vest resolver:

1. Несовпадение имён полей Если test('email') не совпадает с register('email'), ошибка не будет отображена.

2. Отсутствие возврата данных из suite Если Vest-сессия не возвращает корректный результат, resolver не сможет сформировать errors.

3. Асинхронные тесты без await внутри enforce Нарушение приводит к некорректной агрегации ошибок.


Особенности поведения при динамических формах

При использовании динамических полей (например, массивов через useFieldArray в React Hook Form):

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

Внутренний цикл обработки данных

Полный цикл работы Vest resolver:

  1. Получение данных формы из React Hook Form
  2. Запуск Vest suite с текущими значениями
  3. Выполнение всех test/async test
  4. Сбор результата выполнения
  5. Нормализация структуры ошибок
  6. Возврат объекта в React Hook Form
  7. Обновление состояния errors и rerender UI

Этот цикл выполняется синхронно или асинхронно в зависимости от наличия async тестов.


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

Vest resolver особенно эффективен в сценариях:

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

В таких случаях императивная природа Vest обеспечивает большую гибкость по сравнению с чисто декларативными схемами.