Зависимые поля в схемах валидации становятся критическим механизмом, когда значение одного поля определяет правила проверки другого. В связке Yup и YupResolver такая логика реализуется на уровне схемы, а не в компоненте формы, что позволяет сохранять декларативность и предсказуемость поведения валидации.
Основная идея заключается в том, что схема описывает не только
статические ограничения (например, обязательность или формат), но и
динамические зависимости между значениями. В контексте Yup это
достигается через условные конструкции, такие как when,
ссылки через ref, а также пользовательские валидаторы
test.
При использовании YupResolver схема получает актуальные значения формы при каждом изменении состояния. Resolver выступает промежуточным слоем между библиотекой управления формой (например, React Hook Form) и Yup-схемой. На вход передаются:
Далее Yup выполняет синхронную или асинхронную валидацию, возвращая структурированный результат ошибок, где каждое поле связано с конкретным сообщением.
Ключевой момент: зависимость полей не требует ручного пересчёта состояния — она описывается внутри схемы и автоматически пересчитывается при изменении значений.
whenНаиболее распространённый механизм зависимых полей — метод
when. Он позволяет изменять правила валидации одного поля в
зависимости от значения другого.
Простейший пример: поле passportNumber становится
обязательным только если hasPassport равно
true.
import * as Yup from "yup";
const schema = Yup.object({
hasPassport: Yup.boolean(),
passportNumber: Yup.string().when("hasPassport", {
is: true,
then: (schema) => schema.required("Укажите номер паспорта"),
otherwise: (schema) => schema.notRequired()
})
});
Здесь происходит декларативное описание зависимости: поле
passportNumber не хранит собственную логику, оно полностью
подчиняется состоянию hasPassport.
Внутри YupResolver это обрабатывается на этапе валидации: при каждом
изменении hasPassport пересчитывается валидность
passportNumber.
Более сложные сценарии включают зависимость от нескольких источников
данных. Yup поддерживает передачу массива зависимостей в
when.
Пример: поле deliveryAddress обязательно, если выбран
способ доставки courier и заказ не является
самовывозом.
const schema = Yup.object({
deliveryMethod: Yup.string(),
isPickup: Yup.boolean(),
deliveryAddress: Yup.string().when(
["deliveryMethod", "isPickup"],
([deliveryMethod, isPickup], schema) => {
return deliveryMethod === "courier" && !isPickup
? schema.required("Укажите адрес доставки")
: schema.notRequired();
}
)
});
Такая конструкция позволяет формировать сложные правила без разрастания логики в компоненте формы.
ref для связи полейref в Yup применяется для прямого обращения к значению
другого поля внутри валидации. Это особенно полезно при проверке
паролей, диапазонов дат и числовых ограничений.
Пример: подтверждение пароля должно совпадать с основным паролем.
const schema = Yup.object({
password: Yup.string().required(),
confirmPassword: Yup.string().oneOf(
[Yup.ref("password")],
"Пароли не совпадают"
)
});
Здесь Yup.ref("password") создаёт динамическую ссылку на
текущее значение поля password. При любом изменении пароля
пересчитывается валидность подтверждения.
В контексте YupResolver это особенно важно: пересчёт происходит автоматически без необходимости вручную триггерить валидацию зависимых полей.
contextИногда зависимость полей выходит за рамки значений формы. В таких
случаях используется context, который передаётся в resolver
и доступен внутри схемы.
Пример: валидация зависит от роли пользователя.
const schema = Yup.object({
discountCode: Yup.string().when("$role", (role, schema) => {
return role === "admin"
? schema.notRequired()
: schema.required("Введите промокод");
})
});
При использовании YupResolver контекст передаётся следующим образом:
useForm({
resolver: yupResolver(schema, { context: { role: "user" } })
});
Такой подход отделяет бизнес-логику от структуры формы и позволяет внедрять внешние параметры без изменения схемы.
В сложных формах часто возникает цепочка зависимостей, где одно поле влияет на второе, а второе — на третье. Например:
const schema = Yup.object({
accountType: Yup.string(),
companyName: Yup.string().when("accountType", {
is: "business",
then: (s) => s.required(),
otherwise: (s) => s.notRequired()
}),
vatNumber: Yup.string().when(["accountType", "companyName"], {
is: (accountType, companyName) =>
accountType === "business" && !!companyName,
then: (s) => s.required("Укажите VAT номер"),
otherwise: (s) => s.notRequired()
})
});
Такие схемы требуют аккуратного проектирования, поскольку увеличение числа зависимостей повышает сложность предсказания состояния валидации. Однако декларативная природа Yup сохраняет читаемость по сравнению с императивными проверками в компоненте.
testКогда логика зависимости выходит за пределы стандартных методов,
используется test. Он предоставляет полный доступ к
значению поля и всей родительской структуре.
Пример: поле должно быть больше другого поля.
const schema = Yup.object({
minValue: Yup.number(),
maxValue: Yup.number().test(
"is-greater",
"maxValue должен быть больше minValue",
function (value) {
const { minValue } = this.parent;
return value > minValue;
}
)
});
Здесь this.parent предоставляет доступ ко всем полям
объекта. Это делает возможной реализацию произвольных зависимостей,
которые невозможно выразить через when.
При каждом изменении значения формы YupResolver вызывает повторную валидацию схемы. Важно, что Yup не отслеживает изменения реактивно — пересчёт полностью управляется внешним слоем.
Это означает:
Такой подход упрощает архитектуру, но требует аккуратной работы с производительностью в больших формах.
При сложных зависимостях возможны ситуации, когда несколько правил конфликтуют друг с другом. Например, одно правило делает поле обязательным, другое — запрещает его заполнение.
field: Yup.string()
.when("flag", {
is: true,
then: (s) => s.required(),
otherwise: (s) => s.notRequired()
})
.when("disabled", {
is: true,
then: (s) => s.strip()
})
В подобных случаях порядок применения условий имеет значение, поскольку каждое последующее преобразование модифицирует итоговую схему. Это требует строгого контроля логики на уровне архитектуры формы.
В связке с React Hook Form зависимые поля получают дополнительное поведение за счёт подписки на изменения.
const { watch } = useForm({
resolver: yupResolver(schema)
});
const accountType = watch("accountType");
Хотя watch позволяет реагировать на изменения вне схемы,
при использовании YupResolver предпочтительно переносить всю зависимую
логику в Yup, чтобы избежать дублирования правил.
Схема остаётся единственным источником истины, а UI лишь отображает её состояние.
При большом количестве зависимых полей возникает риск избыточных пересчётов. Чтобы снизить нагрузку, используются следующие подходы:
when;object().shape;lazy для отложенного построения
схем;const requiredIfBusiness = (schema) =>
schema.when("accountType", {
is: "business",
then: (s) => s.required(),
otherwise: (s) => s.notRequired()
});
Такой подход снижает дублирование и улучшает читаемость при сохранении декларативной структуры.
Yup поддерживает асинхронную валидацию через test, что
позволяет реализовывать зависимости от внешних данных, например проверку
уникальности.
username: Yup.string().test(
"unique",
"Имя уже занято",
async (value) => {
const result = await api.checkUsername(value);
return result.available;
}
)
В сочетании с YupResolver такие проверки становятся частью общей схемы, однако увеличивают время валидации и могут влиять на UX при частых изменениях полей.
При изменении значения поля, от которого зависят другие поля, Yup пересчитывает ошибки для всей ветки схемы. Это означает, что:
Такой механизм исключает рассинхронизацию данных, характерную для ручных проверок в компонентной логике.