Валидация форм в современных JavaScript-приложениях строится вокруг
идеи отделения схемы данных от логики UI. В экосистеме React Hook Form
эта задача решается через механизм resolver, который выступает
промежуточным слоем между формой и библиотеками схемной валидации. В
случае Zod используется специализированный адаптер
zodResolver, входящий в пакет
@hookform/resolvers.
Resolver выполняет ключевую роль: он преобразует данные формы в формат, понятный валидатору, запускает проверку, а затем приводит результат к структуре, которую ожидает React Hook Form.
Механизм zodResolver строится вокруг последовательного
выполнения трёх этапов:
1. Получение значений формы React Hook Form передаёт текущее состояние формы в resolver как plain object.
2. Валидация через Zod-схему Zod выполняет синхронную или асинхронную проверку данных на основе описанной схемы.
3. Нормализация результата Ошибки преобразуются в формат RHF, включающий:
path)message)type)Использование начинается с установки зависимостей:
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 строит валидацию через декларативное описание структуры данных.
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
Каждое поле схемы строго типизировано, что позволяет одновременно решать задачи:
const {
register,
handleSubmit,
formState: { errors }
} = useForm({
resolver: zodResolver(schema),
});
Resolver передаётся как функция, связывающая форму и схему.
Ошибки, возвращаемые zodResolver, нормализуются в
объект:
errors.email?.message
errors.password?.message
Каждая ошибка содержит:
message — текст ошибки из Zodtype — тип нарушения (например
invalid_string)ref — ссылка на DOM-элемент (при наличии)Одним из ключевых преимуществ 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),
});
Так достигается синхронизация:
Zod Resolver корректно обрабатывает сложные структуры:
const schema = z.object({
user: z.object({
name: z.string(),
contacts: z.object({
email: z.string().email(),
phone: z.string().optional(),
}),
}),
});
Ошибки в этом случае возвращаются с вложенными путями:
user.nameuser.contacts.emailZod поддерживает массивы через 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.
Zod Resolver работает в любых режимах RHF:
onSubmit — проверка при отправкеonChange — проверка при измененииonBlur — проверка при потере фокусаall — комбинированный режимuseForm({
resolver: zodResolver(schema),
mode: "onChange",
});
Zod Resolver концептуально отличается от Yup подхода:
Типизация
API
Безопасность типов
Производительность
Zod Resolver поддерживает трансформации:
const schema = z.object({
price: z.string().transform((val) => Number(val)),
});
После прохождения resolver данные в handleSubmit уже
преобразованы.
Если входные данные не соответствуют типу:
z.number()
и приходит строка, Zod формирует ошибку парсинга, которая попадает в RHF как стандартная validation error без необходимости дополнительной обработки.
Resolver корректно работает при инициализации формы:
useForm({
defaultValues: {
user: {
name: "",
contacts: {
email: "",
},
},
},
resolver: zodResolver(schema),
});
Это важно для гидратации серверных данных и редактирования форм.
При больших схемах важны следующие аспекты:
z.lazy для рекурсивных структурrefine с async вызовами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 выполняет нормализацию:
{ values, errors }Эта цепочка обеспечивает предсказуемость поведения даже при сложных схемах.
При использовании z.partial() или
schema.partial() resolver допускает частичную проверку:
const schema = z.object({
email: z.string().email(),
password: z.string(),
}).partial();
Это используется в формах редактирования профиля, где часть данных может отсутствовать.
Resolver не зависит от UI-слоя, поэтому одинаково работает с:
Ключевое условие — корректная регистрация через register
или Controller.
В многошаговых формах Zod Resolver применяется частично:
const step1Schema = schema.pick({ email: true });
const step2Schema = schema.pick({ password: true });
Каждый шаг использует отдельный resolver, что снижает нагрузку и упрощает контроль ошибок.