При проектировании форм с несколькими уровнями данных часто возникает необходимость моделировать вложенные структуры. В таких случаях данные формы перестают быть плоскими и переходят в иерархический формат, где один объект содержит другие объекты с собственными наборами полей. Библиотека Yup предоставляет встроенные механизмы для описания таких структур, а YupResolver обеспечивает их интеграцию с системами управления формами, такими как React Hook Form.
Вложенные объекты в Yup описываются через комбинацию
object() и shape(), позволяя формировать
строгую валидационную схему для каждого уровня данных.
Основой для вложенных структур служит метод
Yup.object():
import * as Yup from "yup";
const schema = Yup.object({
user: Yup.object({
name: Yup.string().required(),
age: Yup.number().min(0).required()
})
});
В этом примере создаётся объект user, содержащий два
поля. Каждый уровень вложенности описывается независимо, но объединяется
в единую схему.
Ключевая особенность заключается в том, что Yup рекурсивно проходит по структуре объекта, валидируя каждый уровень согласно заданным правилам.
При использовании React Hook Form схема Yup подключается через
yupResolver, который преобразует Yup-валидацию в формат,
понятный системе управления формами.
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
const form = useForm({
resolver: yupResolver(schema)
});
При передаче вложенных объектов структура данных формы должна строго соответствовать схеме. Несоответствие структуры приводит к тому, что поля внутри вложенных объектов не проходят валидацию или становятся недоступными для регистрации.
Для работы с вложенными объектами в React Hook Form используется точечная нотация:
register("user.name");
register("user.age");
Такая запись позволяет системе корректно сопоставить значения формы с вложенной структурой Yup.
При этом важно учитывать, что имя поля должно полностью соответствовать пути в объекте схемы. Любое отклонение приводит к рассинхронизации данных.
Yup поддерживает произвольную глубину вложенности, что позволяет моделировать сложные структуры:
const schema = Yup.object({
company: Yup.object({
info: Yup.object({
title: Yup.string().required(),
address: Yup.object({
city: Yup.string().required(),
zip: Yup.string().required()
})
})
})
});
Такая схема представляет трёхуровневую структуру, где каждый уровень является самостоятельным объектом с собственной валидацией.
При использовании YupResolver данные формы должны повторять аналогичную структуру:
{
company: {
info: {
title: "",
address: {
city: "",
zip: ""
}
}
}
}
Корректная работа вложенных структур требует явного задания
defaultValues. Отсутствие промежуточных объектов приводит к
ошибкам доступа к свойствам.
const defaultValues = {
company: {
info: {
title: "",
address: {
city: "",
zip: ""
}
}
}
};
YupResolver не создаёт структуру автоматически, поэтому инициализация должна полностью соответствовать схеме.
Обязательные поля внутри вложенных объектов определяются стандартным
методом required():
const schema = Yup.object({
profile: Yup.object({
contact: Yup.object({
email: Yup.string().email().required(),
phone: Yup.string().required()
})
})
});
Каждое поле валидируется независимо, однако ошибка возвращается с указанием полного пути:
profile.contact.emailprofile.contact.phoneЭто обеспечивает точную локализацию ошибок на уровне UI.
В некоторых сценариях вложенные объекты могут быть необязательными
целиком. Для этого используется nullable() или
notRequired():
const schema = Yup.object({
settings: Yup.object({
theme: Yup.string().required()
}).notRequired()
});
В этом случае отсутствие объекта settings не приводит к
ошибке, однако при его наличии структура должна соответствовать
описанию.
shape() для явного описания структурыМетод shape() часто применяется для повышения читаемости
схем:
const addressSchema = Yup.object().shape({
city: Yup.string().required(),
country: Yup.string().required()
});
const userSchema = Yup.object().shape({
name: Yup.string().required(),
address: addressSchema
});
Разделение схем на компоненты упрощает повторное использование и поддержку сложных вложенных структур.
При использовании TypeScript важно синхронизировать типы с Yup-схемой:
type Address = {
city: string;
country: string;
};
type User = {
name: string;
address: Address;
};
Далее схема связывается с типом:
const schema: Yup.SchemaOf<User> = Yup.object({
name: Yup.string().required(),
address: Yup.object({
city: Yup.string().required(),
country: Yup.string().required()
})
});
YupResolver автоматически выводит тип формы, сохраняя согласованность между схемой и данными.
Ошибки, возвращаемые YupResolver, сохраняют структуру объекта, что позволяет удобно отображать их в интерфейсе:
{
profile: {
contact: {
email: "Неверный формат email"
}
}
}
Такой формат позволяет напрямую привязывать сообщения об ошибках к конкретным полям без дополнительной обработки.
В сложных формах структура вложенных объектов может изменяться
динамически. Для таких случаев используется lazy():
const schema = Yup.object({
data: Yup.lazy((value) =>
Yup.object(
Object.keys(value || {}).reduce((acc, key) => {
acc[key] = Yup.object({
value: Yup.string().required()
});
return acc;
}, {})
)
)
});
Такой подход позволяет валидировать непредсказуемые структуры, сохраняя совместимость с YupResolver.
При работе с большими формами важно учитывать производительность и читаемость схем. Разбиение структуры на модули снижает сложность:
const personalInfoSchema = Yup.object({
firstName: Yup.string().required(),
lastName: Yup.string().required()
});
const jobSchema = Yup.object({
position: Yup.string().required(),
department: Yup.string().required()
});
const employeeSchema = Yup.object({
personal: personalInfoSchema,
job: jobSchema
});
Такая композиция позволяет управлять вложенностью без перегрузки одного объекта.
YupResolver выполняет рекурсивную проверку всей структуры. При обнаружении ошибки процесс продолжается, чтобы собрать полный список нарушений. Это позволяет получить полную картину состояния формы, а не останавливаться на первой ошибке.
Особенность заключается в том, что вложенные ошибки нормализуются в объект, соответствующий пути полей, что делает их совместимыми с механизмами регистрации полей React Hook Form.
Ключевым требованием при работе с вложенными объектами является полная идентичность структуры:
Любое несоответствие приводит к потере связи между уровнями данных и нарушению валидации.
Пустые вложенные объекты требуют явного определения поведения:
const schema = Yup.object({
meta: Yup.object({
tags: Yup.array().of(Yup.string())
}).default(undefined)
});
При отсутствии значений важно различать undefined и
пустой объект {}, поскольку Yup трактует их по-разному.
При росте формы вложенные объекты становятся инструментом декомпозиции доменной модели. Каждая часть схемы начинает соответствовать отдельной предметной области, что позволяет поддерживать независимость модулей.
Схема перестаёт быть монолитной и превращается в дерево, где каждая ветвь описывает собственный контекст данных, а YupResolver обеспечивает единообразную валидацию всей структуры независимо от её глубины.