Архитектура современных веб-приложений требует строгого разграничения обязанностей между слоями логики. В контексте работы с формами в JavaScript-экосистеме это особенно заметно при использовании связки React Hook Form и Yup через YupResolver. Ключевая идея заключается в том, чтобы отделить управление состоянием формы от описания правил валидации, сохраняя каждую часть системы максимально изолированной и переиспользуемой.
Разделение ответственности строится на базовом принципе: каждый модуль должен отвечать только за одну задачу. В контексте форм можно выделить три независимых уровня:
Без такого разделения логика формы быстро становится монолитной: проверки перемешиваются с UI, обработчики событий дублируют правила валидации, а тестирование усложняется.
Yup выступает как самостоятельный слой описания схемы данных. Он не знает ничего о React, DOM или конкретной форме. Его задача — описать структуру и ограничения данных.
Пример схемы:
import * as Yup from "yup";
const userSchema = Yup.object({
email: Yup.string()
.email("Некорректный email")
.required("Email обязателен"),
password: Yup.string()
.min(8, "Минимум 8 символов")
.required("Пароль обязателен"),
});
Здесь вся логика валидации сосредоточена в одном месте. Она не зависит от UI и может быть использована в любом контексте: клиентском, серверном или тестовом.
React Hook Form отвечает за жизненный цикл формы:
При этом библиотека сознательно избегает жесткой привязки к конкретному способу валидации. Это позволяет подключать внешние схемы через механизм резолверов.
import { useForm } from "react-hook-form";
const { register, handleSubmit, formState } = useForm();
Без резолвера React Hook Form не знает, какие правила применять — он лишь предоставляет инфраструктуру.
YupResolver выполняет роль адаптера между декларативной схемой Yup и системой React Hook Form. Он не содержит бизнес-логики и не описывает правила валидации. Его задача — преобразовать результат работы Yup в формат, понятный форме.
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(),
});
const form = useForm({
resolver: yupResolver(schema),
});
В этом случае:
Каждый слой остается независимым.
Четкое разделение становится заметным при анализе обязанностей:
Yup:
React Hook Form:
YupResolver:
Ни один из слоев не дублирует функциональность другого.
При отсутствии разделения часто возникает ситуация, когда:
Пример антипаттерна:
const validateEmail = (value) => {
if (!value) return "Email обязателен";
if (!value.includes("@")) return "Некорректный email";
return true;
};
Такая проверка привязана к конкретному полю и месту использования, что делает её непереиспользуемой.
Перенос логики в Yup устраняет дублирование и упрощает сопровождение:
const schema = Yup.object({
email: Yup.string().email().required(),
age: Yup.number().min(18).required(),
});
Теперь любые изменения в правилах происходят в одном месте, не затрагивая компоненты интерфейса.
YupResolver обеспечивает важный архитектурный эффект: форма не зависит от конкретной библиотеки валидации. Вместо прямого вызова проверок используется абстракция — resolver.
Это позволяет:
const form = useForm({
resolver: yupResolver(schema),
});
Форма знает только о resolver, но не о внутренней реализации.
В крупных приложениях формы часто содержат десятки полей и вложенные структуры. Разделение ответственности становится критически важным.
Yup позволяет описывать вложенные схемы:
const schema = Yup.object({
user: Yup.object({
name: Yup.string().required(),
contacts: Yup.object({
email: Yup.string().email().required(),
}),
}),
});
React Hook Form при этом остается неизменным, а YupResolver корректно обрабатывает структуру ошибок.
Одним из ключевых преимуществ разделения ответственности является возможность повторного использования схемы в разных контекстах:
Схема перестает быть частью интерфейса и становится описанием данных как доменной модели.
Yup поддерживает композицию, что усиливает разделение ответственности на уровне домена:
const baseUser = Yup.object({
email: Yup.string().email().required(),
});
const extendedUser = baseUser.shape({
role: Yup.string().required(),
});
Такой подход позволяет строить сложные модели без дублирования логики и без привязки к формам.
Ошибки, возвращаемые YupResolver, становятся формализованным контрактом между валидацией и UI. React Hook Form не интерпретирует ошибки — он лишь отображает их состояние.
Структура ошибки:
Это позволяет UI оставаться максимально простым: он не принимает решений, а только отображает результат работы слоя валидации.
Разделение ответственности в связке Yup, YupResolver и React Hook Form приводит к архитектуре, в которой:
Такое распределение обязанностей снижает связность кода и делает систему предсказуемой при росте сложности.