Валидационные схемы на основе Yup традиционно воспринимаются как статические структуры: набор полей, правил и ограничений, описанных один раз. Однако в реальных приложениях форма часто зависит от состояния интерфейса, выбранных пользователем опций, роли, типа сущности или внешних данных. Это приводит к необходимости формирования схемы на лету.
При использовании react-hook-form через YupResolver, динамичность схемы становится не просто возможностью, а архитектурной задачей: resolver пересобирает правила валидации при изменении условий, обеспечивая актуальность проверок без ручного управления логикой валидации.
Ключевая идея динамических схем заключается в том, что структура Yup-валидации является функцией входных параметров состояния, а не фиксированным объектом.
Базовый паттерн динамических схем — фабрика схемы. Схема представляется как функция, возвращающая Yup-объект в зависимости от входных параметров.
import * as yup from "yup";
const createSchema = (mode) => {
return yup.object({
email: yup.string().email().required(),
password: mode === "register"
? yup.string().min(8).required()
: yup.string().notRequired(),
confirmPassword: mode === "register"
? yup.string()
.oneOf([yup.ref("password")], "Пароли должны совпадать")
.required()
: yup.string().notRequired(),
});
};
Такой подход позволяет:
Однако при использовании с YupResolver важно учитывать момент пересоздания схемы, так как каждый новый объект запускает повторную инициализацию логики валидации.
В связке с react-hook-form схема передаётся через resolver, и любое изменение схемы должно приводить к обновлению формы.
import { useMemo } from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
const formMode = "register";
const schema = useMemo(() => createSchema(formMode), [formMode]);
const form = useForm({
resolver: yupResolver(schema),
mode: "onChange",
});
Использование useMemo критично: без мемоизации каждая
перерисовка компонента создаёт новую схему, что приводит к:
Динамические схемы часто усложняются условиями, зависящими от
значений других полей. Yup предоставляет механизм when,
позволяющий строить зависимости внутри самой схемы.
const schema = yup.object({
hasCompany: yup.boolean(),
companyName: yup.string().when("hasCompany", {
is: true,
then: (schema) => schema.required("Название компании обязательно"),
otherwise: (schema) => schema.notRequired(),
}),
});
Такая конструкция переносит логику из UI в слой валидации, уменьшая связность компонентов.
Помимо значений формы, схема может зависеть от внешних факторов:
const buildSchema = ({ isAdmin, country }) =>
yup.object({
username: yup.string().required(),
taxId: country === "KZ"
? yup.string().length(12)
: yup.string().notRequired(),
permissions: isAdmin
? yup.array().min(1)
: yup.array().strip(),
});
Использование strip() позволяет исключать поля из
результирующего объекта, что важно при серверной отправке данных.
При динамических схемах ключевой архитектурной проблемой становится момент пересоздания resolver. React Hook Form не всегда ожидает смену схемы в рантайме, поэтому требуется контролируемое обновление формы.
const schema = useMemo(
() => buildSchema({ isAdmin, country }),
[isAdmin, country]
);
const form = useForm({
resolver: yupResolver(schema),
});
При изменении зависимостей возможны два подхода:
В более сложных системах схема не пересоздаётся целиком, а изменяется частично через композицию.
const baseSchema = yup.object({
email: yup.string().email().required(),
});
const adminExtension = yup.object({
role: yup.string().required(),
permissions: yup.array().min(1),
});
const schema = isAdmin
? baseSchema.concat(adminExtension)
: baseSchema;
Такой подход снижает стоимость пересборки и улучшает читаемость логики.
Особую сложность представляют схемы с массивами и вложенными объектами, где структура зависит от пользовательского ввода.
const schema = yup.object({
items: yup.array().of(
yup.object({
type: yup.string().required(),
value: yup.string().when("type", {
is: "text",
then: (s) => s.required(),
otherwise: (s) => s.notRequired(),
}),
})
),
});
Такие схемы часто применяются в:
Динамическая генерация схемы имеет прямое влияние на производительность, особенно при частых рендерах.
Основные оптимизации:
1. Мемоизация схемы
2. Разделение схем
3. Ленивая сборка
4. Минимизация вложенности when
В TypeScript динамические схемы требуют аккуратного согласования типов формы и результата валидации.
type FormValues = {
mode: "basic" | "advanced";
email: string;
password?: string;
};
const createSchema = (mode: FormValues["mode"]) =>
yup.object({
email: yup.string().required(),
password: mode === "advanced"
? yup.string().required()
: yup.string().notRequired(),
});
При использовании динамики важно учитывать, что тип схемы становится условным и не всегда может быть строго выведен автоматически.
В сложных приложениях динамические схемы становятся отражением бизнес-правил. При этом важно отделять:
Нарушение этого разделения приводит к появлению «монолитных схем», которые трудно тестировать и поддерживать.
Типовые проблемы:
when