При работе с формами в прикладных JavaScript-приложениях схемы валидации быстро разрастаются и начинают повторяться между экранами. Разбиение схем на независимые части позволяет управлять сложностью и повторным использованием логики валидации без дублирования.
Базовый принцип — выделение устойчивых фрагментов схемы в отдельные модули:
import * as Yup from "yup";
export const emailField = Yup.string()
.email("Некорректный формат email")
.required("Email обязателен");
export const passwordField = Yup.string()
.min(8, "Минимум 8 символов")
.required("Пароль обязателен");
Такие фрагменты становятся строительными блоками для более сложных
объектов. В контексте YupResolver это особенно важно, так
как резолвер работает с итоговой схемой целиком, а не с её частями.
Основной способ сборки схемы в Yup — использование
object().shape(). Он позволяет комбинировать ранее
определённые поля в единый объект.
import * as Yup from "yup";
import { emailField, passwordField } from "./fields";
export const loginSchema = Yup.object().shape({
email: emailField,
password: passwordField,
});
Композиция здесь линейная: каждый ключ объекта соответствует
независимой валидационной цепочке. При передаче этой схемы в
YupResolver она интерпретируется как единое правило
валидации формы.
import { yupResolver } from "@hookform/resolvers/yup";
import { useForm } from "react-hook-form";
const form = useForm({
resolver: yupResolver(loginSchema),
});
Композиция становится более гибкой, когда отдельные схемы описывают не только поля, но и целые подструктуры данных.
export const addressSchema = Yup.object().shape({
city: Yup.string().required(),
street: Yup.string().required(),
zip: Yup.string().required(),
});
Эта схема может быть встроена в другие схемы как вложенный объект:
export const userSchema = Yup.object().shape({
name: Yup.string().required(),
address: addressSchema,
});
При таком подходе YupResolver корректно обрабатывает
вложенные ошибки, возвращая структуру ошибок, соответствующую форме
данных.
При сложных формах данные часто имеют глубоко вложенную структуру.
Yup поддерживает композицию через вложенные object() и
array().
export const orderSchema = Yup.object().shape({
customer: Yup.object().shape({
name: Yup.string().required(),
email: Yup.string().email().required(),
}),
items: Yup.array().of(
Yup.object().shape({
productId: Yup.string().required(),
quantity: Yup.number().min(1).required(),
})
),
});
Такая структура напрямую отражается в модели данных и сохраняется при
обработке через YupResolver, что упрощает интеграцию с
react-hook-form.
Схемы можно не только импортировать, но и расширять через функции-фабрики. Это позволяет создавать вариативные формы на основе базовой структуры.
const baseUserSchema = {
name: Yup.string().required(),
email: Yup.string().email().required(),
};
export const createUserSchema = (withPassword = false) =>
Yup.object().shape({
...baseUserSchema,
...(withPassword
? { password: Yup.string().min(8).required() }
: {}),
});
Такой подход особенно полезен для сценариев «создание/редактирование», где часть полей может быть опциональной или отсутствовать.
Yup не предоставляет полноценного механизма глубокого merge схем,
поэтому композиция часто реализуется вручную через spread-операторы и
повторное использование shape.
Важно учитывать, что поверхностное слияние работает только на уровне объекта:
const schemaA = Yup.object().shape({
name: Yup.string().required(),
});
const schemaB = Yup.object().shape({
age: Yup.number().required(),
});
const merged = Yup.object().shape({
...schemaA.fields,
...schemaB.fields,
});
Такой подход требует аккуратности: при совпадении ключей последняя схема перезапишет предыдущую.
Одним из ключевых инструментов композиции является when,
позволяющий динамически изменять структуру схемы в зависимости от других
полей.
export const profileSchema = Yup.object().shape({
role: Yup.string().required(),
companyName: Yup.string().when("role", {
is: "business",
then: (schema) => schema.required(),
otherwise: (schema) => schema.notRequired(),
}),
});
В контексте YupResolver это позволяет описывать сложные
бизнес-правила без внешней логики в форме.
Для случаев, когда структура данных зависит от рантайм-значений,
используется Yup.lazy.
const dynamicSchema = Yup.lazy((value) => {
if (value?.type === "A") {
return Yup.object().shape({
fieldA: Yup.string().required(),
});
}
return Yup.object().shape({
fieldB: Yup.number().required(),
});
});
Это расширяет композицию до уровня условного построения схемы целиком, что полезно для динамических форм и конфигурируемых интерфейсов.
Массивы требуют отдельного подхода, так как каждый элемент должен иметь собственную схему валидации.
const tagsSchema = Yup.array().of(
Yup.object().shape({
id: Yup.string().required(),
label: Yup.string().required(),
})
);
Композиция здесь выражается через повторяемую схему элемента, которая может быть переиспользована в других структурах:
const tagSchema = Yup.object().shape({
id: Yup.string().required(),
label: Yup.string().required(),
});
const postSchema = Yup.object().shape({
tags: Yup.array().of(tagSchema),
});
YupResolver не изменяет семантику схемы, но определяет
способ её интерпретации в контексте формы. Композиция схем напрямую
влияет на структуру ошибок, возвращаемых резолвером.
import { yupResolver } from "@hookform/resolvers/yup";
const form = useForm({
resolver: yupResolver(userSchema),
});
Ошибки наследуют структуру схемы:
Это делает композицию не только архитектурным приёмом, но и способом управления структурой ошибок UI.
При масштабировании приложений схемы начинают выполнять роль доменного слоя. Их композиция превращается в декларативное описание бизнес-правил.
export const createAuthSchema = ({ requirePhone }) =>
Yup.object().shape({
email: emailField,
password: passwordField,
phone: requirePhone
? Yup.string().required()
: Yup.string().notRequired(),
});
Такие фабрики позволяют изолировать вариативность правил и сохранять единый источник истины для валидации.
Схема перестаёт быть просто валидатором и становится частью контрактной модели данных. При композиции важно удерживать границы:
Нарушение этих границ приводит к разрастанию зависимостей между
схемами и усложняет использование YupResolver в разных
контекстах приложения.