Связка схемной валидации и форменной логики строится через
resolver-слой, который преобразует правила проверки Zod в формат,
понятный системе управления формами. В экосистеме React Hook Form это
реализуется через пакет @hookform/resolvers/zod,
обеспечивающий синхронизацию состояния формы и результатов
валидации.
Основной принцип архитектуры заключается в разделении ответственности: React Hook Form управляет состоянием и подписками на поля, Zod описывает строгую структуру данных и правила их проверки.
Определение схемы строится декларативно:
import { z } from "zod";
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
Интеграция с формой:
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
const form = useForm({
resolver: zodResolver(schema),
});
Валидация начинает выполняться на уровне resolver-а, а React Hook Form получает уже нормализованные ошибки.
Одним из ключевых преимуществ является автоматическое выведение типов:
type FormData = z.infer<typeof schema>;
Эта конструкция устраняет дублирование интерфейсов и схем. Типы и правила становятся единым источником истины.
Каждое поле связывается с системой через register:
const { register, handleSubmit, formState: { errors } } = form;
<input {...register("email")} />
<input {...register("password")} />
Ошибки, возвращаемые Zod, автоматически отображаются в
formState.errors:
{errors.email?.message}
{errors.password?.message}
Zod поддерживает вложенные структуры, которые напрямую отображаются в форме:
const schema = z.object({
user: z.object({
name: z.string(),
age: z.number().min(18),
}),
});
Использование в форме:
<input {...register("user.name")} />
<input {...register("user.age", { valueAsNumber: true })} />
Ключевой момент заключается в синхронизации точечной нотации React Hook Form с деревом объектов Zod.
Работа с коллекциями реализуется через
useFieldArray:
import { useFieldArray } from "react-hook-form";
const { fields, append, remove } = useFieldArray({
control,
name: "items",
});
Схема:
const schema = z.object({
items: z.array(
z.object({
title: z.string(),
quantity: z.number(),
})
),
});
Каждый элемент массива валидируется независимо, но в рамках общей структуры.
Для нестандартных компонентов используется
Controller:
import { Controller } from "react-hook-form";
<Controller
name="category"
control={control}
render={({ field }) => (
<Select {...field} />
)}
/>
Zod продолжает контролировать тип и ограничения, несмотря на абстракцию UI-компонента.
Zod позволяет изменять данные до попадания в форму результата:
const schema = z.object({
price: z.string().transform((val) => Number(val)),
});
Такой подход устраняет необходимость ручного парсинга данных после submit.
Сложные бизнес-правила реализуются через refine:
const schema = z.object({
password: z.string(),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
path: ["confirmPassword"],
message: "Пароли не совпадают",
});
Ошибки привязываются к конкретному полю, сохраняя консистентность UI.
Несмотря на синхронную природу Zod, React Hook Form допускает асинхронные проверки:
const schema = z.object({
username: z.string().refine(async (value) => {
const res = await fetch(`/api/check?u=${value}`);
return res.ok;
}, {
message: "Имя занято",
}),
});
Такая логика увеличивает нагрузку, поэтому часто комбинируется с debounce на уровне UI.
React Hook Form поддерживает несколько стратегий:
onSubmitonChangeonBlurallПример конфигурации:
useForm({
resolver: zodResolver(schema),
mode: "onBlur",
});
Выбор режима влияет на момент запуска схемной проверки Zod.
Схемы Zod допускают композицию:
const baseUser = z.object({
email: z.string().email(),
});
const extendedUser = baseUser.extend({
role: z.string(),
});
Такой подход снижает дублирование логики между формами.
Зависимость полей выражается через superRefine:
const schema = z.object({
hasDiscount: z.boolean(),
discountCode: z.string().optional(),
}).superRefine((data, ctx) => {
if (data.hasDiscount && !data.discountCode) {
ctx.addIssue({
path: ["discountCode"],
message: "Требуется код скидки",
});
}
});
Одной из ключевых проблем является несовпадение типов HTML input и строгой типизации Zod. Решается через:
valueAsNumbervalueAsDateОшибки Zod структурированы и глубоко вложены:
errors.user?.name?.message
React Hook Form сохраняет иерархию, соответствующую схеме, без потери контекста.
Связка с Zod не влияет напрямую на перерисовки, но влияет на частоту валидаций. Оптимизация достигается через:
shouldUnregisteruseFormStatemode: "onChange"Схемы Zod часто становятся представлением доменной модели:
const OrderSchema = z.object({
items: z.array(
z.object({
id: z.string(),
quantity: z.number().min(1),
})
),
total: z.number(),
});
React Hook Form в таком подходе выступает слоем взаимодействия, не содержащим бизнес-логики.