Zod resolver

Валидация форм в современных JavaScript-приложениях строится вокруг идеи отделения схемы данных от логики UI. В экосистеме React Hook Form эта задача решается через механизм resolver, который выступает промежуточным слоем между формой и библиотеками схемной валидации. В случае Zod используется специализированный адаптер zodResolver, входящий в пакет @hookform/resolvers.

Resolver выполняет ключевую роль: он преобразует данные формы в формат, понятный валидатору, запускает проверку, а затем приводит результат к структуре, которую ожидает React Hook Form.


Принцип работы Zod Resolver

Механизм zodResolver строится вокруг последовательного выполнения трёх этапов:

1. Получение значений формы React Hook Form передаёт текущее состояние формы в resolver как plain object.

2. Валидация через Zod-схему Zod выполняет синхронную или асинхронную проверку данных на основе описанной схемы.

3. Нормализация результата Ошибки преобразуются в формат RHF, включающий:

  • путь поля (path)
  • сообщение об ошибке (message)
  • тип ошибки (type)

Подключение Zod Resolver

Использование начинается с установки зависимостей:

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

Далее подключается сам resolver:

import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";

Базовая схема Zod

Zod строит валидацию через декларативное описание структуры данных.

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

Каждое поле схемы строго типизировано, что позволяет одновременно решать задачи:

  • runtime-валидации
  • статической типизации (в TypeScript)
  • документирования структуры данных

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

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

Resolver передаётся как функция, связывающая форму и схему.


Структура ошибок Zod Resolver

Ошибки, возвращаемые zodResolver, нормализуются в объект:

errors.email?.message
errors.password?.message

Каждая ошибка содержит:

  • message — текст ошибки из Zod
  • type — тип нарушения (например invalid_string)
  • ref — ссылка на DOM-элемент (при наличии)

Типизация через Zod

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

const schema = z.object({
  email: z.string().email(),
  age: z.number().min(18),
});

type FormData = z.infer<typeof schema>;

Этот тип затем используется в useForm:

const { register } = useForm<FormData>({
  resolver: zodResolver(schema),
});

Так достигается синхронизация:

  • схемы
  • типов TypeScript
  • runtime-валидации

Особенности работы с вложенными объектами

Zod Resolver корректно обрабатывает сложные структуры:

const schema = z.object({
  user: z.object({
    name: z.string(),
    contacts: z.object({
      email: z.string().email(),
      phone: z.string().optional(),
    }),
  }),
});

Ошибки в этом случае возвращаются с вложенными путями:

  • user.name
  • user.contacts.email

Работа с массивами

Zod поддерживает массивы через z.array:

const schema = z.object({
  tags: z.array(z.string().min(2)),
});

Resolver формирует ошибки по индексам:

  • tags[0]
  • tags[1]

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


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

Zod поддерживает async-проверки через refine:

const schema = z.object({
  username: z.string().refine(async (val) => {
    const res = await fetch(`/api/check?u=${val}`);
    return res.ok;
  }, {
    message: "Имя пользователя занято",
  }),
});

zodResolver корректно ожидает Promise и интегрирует результат в поток RHF.


Поведение при режимах валидации React Hook Form

Zod Resolver работает в любых режимах RHF:

  • onSubmit — проверка при отправке
  • onChange — проверка при изменении
  • onBlur — проверка при потере фокуса
  • all — комбинированный режим
useForm({
  resolver: zodResolver(schema),
  mode: "onChange",
});

Отличия от Yup Resolver

Zod Resolver концептуально отличается от Yup подхода:

Типизация

  • Zod: нативная поддержка TypeScript
  • Yup: требует ручной интеграции типов

API

  • Zod: функциональная композиция схем
  • Yup: цепочечный (fluent) API

Безопасность типов

  • Zod: строгая проверка на уровне компиляции
  • Yup: частично динамическая типизация

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

  • Zod: быстрее в runtime за счёт упрощённой модели
  • Yup: более тяжёлый runtime-слой

Кастомные трансформации данных

Zod Resolver поддерживает трансформации:

const schema = z.object({
  price: z.string().transform((val) => Number(val)),
});

После прохождения resolver данные в handleSubmit уже преобразованы.


Поведение при ошибках парсинга

Если входные данные не соответствуют типу:

z.number()

и приходит строка, Zod формирует ошибку парсинга, которая попадает в RHF как стандартная validation error без необходимости дополнительной обработки.


Совместимость с nested defaultValues

Resolver корректно работает при инициализации формы:

useForm({
  defaultValues: {
    user: {
      name: "",
      contacts: {
        email: "",
      },
    },
  },
  resolver: zodResolver(schema),
});

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


Оптимизация работы resolver

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

  • использование z.lazy для рекурсивных структур
  • минимизация refine с async вызовами
  • разделение схем на модули
  • избегание избыточных transform цепочек

Обработка conditional validation

Zod позволяет строить условные схемы:

const schema = z.object({
  role: z.string(),
  company: z.string().optional(),
}).superRefine((data, ctx) => {
  if (data.role === "admin" && !data.company) {
    ctx.addIssue({
      path: ["company"],
      message: "Компания обязательна для админа",
    });
  }
});

Resolver корректно агрегирует такие ошибки в стандартный формат RHF.


Внутренняя модель преобразования данных

Zod Resolver выполняет нормализацию:

  1. RHF values → plain object
  2. Zod parse → result object / errors
  3. mapping issues → RHF ErrorMap
  4. return { values, errors }

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


Работа с partial validation

При использовании z.partial() или schema.partial() resolver допускает частичную проверку:

const schema = z.object({
  email: z.string().email(),
  password: z.string(),
}).partial();

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


Интеграция с кастомными UI компонентами

Resolver не зависит от UI-слоя, поэтому одинаково работает с:

  • controlled components
  • uncontrolled inputs
  • custom UI libraries
  • form builders

Ключевое условие — корректная регистрация через register или Controller.


Обработка multi-step форм

В многошаговых формах Zod Resolver применяется частично:

const step1Schema = schema.pick({ email: true });
const step2Schema = schema.pick({ password: true });

Каждый шаг использует отдельный resolver, что снижает нагрузку и упрощает контроль ошибок.