YupResolver — адаптер между библиотекой валидации
Yup и формами, чаще всего используемый вместе с React Hook
Form. Основная задача резолвера — преобразование схемы Yup
в механизм проверки данных формы.
На практике YupResolver:
Yup;Наиболее распространённая реализация подключается через пакет:
npm install yup @hookform/resolvers
Импорт:
import { yupResolver } from "@hookform/resolvers/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("Некорректный email")
.required("Поле обязательно"),
password: yup
.string()
.min(6, "Минимум 6 символов")
.required("Введите пароль")
});
export default function App() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm({
resolver: yupResolver(schema)
});
const onSub mit = data => {
console.log(data);
};
return (
<form onSub mit={handleSubmit(onSubmit)}>
<input {...register("email")} />
<p>{errors.email?.message}</p>
<input type="password" {...register("password")} />
<p>{errors.password?.message}</p>
<button type="submit">Отправить</button>
</form>
);
}
Внутри yupResolver происходит вызов:
schema.validate(data, options)
Если проверка успешна:
{
values: validatedData,
errors: {}
}
Если есть ошибки:
{
values: {},
errors: {
email: {
type: "required",
message: "Поле обязательно"
}
}
}
Именно такой формат ожидает React Hook Form.
React Hook Form позволяет определять момент запуска
YupResolver.
useForm({
resolver: yupResolver(schema),
mode: "onSubmit"
});
useForm({
resolver: yupResolver(schema),
mode: "onChange"
});
useForm({
resolver: yupResolver(schema),
mode: "onBlur"
});
| Режим | Описание |
|---|---|
onSubmit |
Проверка после submit |
onChange |
Проверка при вводе |
onBlur |
Проверка после blur |
all |
Проверка и при blur, и при change |
onTouched |
После первого касания поля |
Резолвер поддерживает дополнительные настройки.
По умолчанию Yup завершает проверку после первой
ошибки.
resolver: yupResolver(schema, {
abortEarly: false
})
Теперь ошибки собираются полностью.
Пример:
const schema = yup.object({
username: yup
.string()
.required("Введите имя")
.min(5, "Минимум 5 символов")
});
Без abortEarly: false пользователь увидит только одну
ошибку.
Удаление лишних полей:
resolver: yupResolver(schema, {
stripUnknown: true
})
Пример:
const schema = yup.object({
name: yup.string()
});
const data = {
name: "Alex",
role: "admin"
};
После валидации:
{
name: "Alex"
}
Отключение автоматических преобразований.
resolver: yupResolver(schema, {
strict: true
})
yup.number().validateSync("25");
Результат:
25
yup.number().strict().validateSync("25");
Ошибка:
this must be a `number` type
YupResolver корректно работает со сложными
структурами.
const schema = yup.object({
profile: yup.object({
firstName: yup.string().required(),
lastName: yup.string().required()
})
});
<input {...register("profile.firstName")} />
<input {...register("profile.lastName")} />
errors.profile?.firstName?.message
const schema = yup.object({
users: yup.array().of(
yup.object({
name: yup.string().required(),
age: yup.number().required()
})
)
});
Регистрация:
<input {...register("users.0.name")} />
<input {...register("users.0.age")} />
Типичная интеграция динамических полей:
import { useFieldArray } from "react-hook-form";
const {
control,
register
} = useForm({
resolver: yupResolver(schema)
});
const { fields, append } = useFieldArray({
control,
name: "skills"
});
Схема:
const schema = yup.object({
skills: yup.array().of(
yup.object({
title: yup.string().required()
})
)
});
YupResolver полностью поддерживает
when.
const schema = yup.object({
isCompany: yup.boolean(),
companyName: yup.string().when("isCompany", {
is: true,
then: schema => schema.required("Введите название компании"),
otherwise: schema => schema.notRequired()
})
});
const schema = yup.object({
password: yup
.string()
.test(
"has-uppercase",
"Нужна заглавная буква",
value => /[A-Z]/.test(value)
)
});
YupResolver умеет работать с async-проверками.
const schema = yup.object({
username: yup.string().test(
"checkUsername",
"Имя уже занято",
async value => {
const response = await fetch(`/api/users/${value}`);
return response.status === 404;
}
)
});
Передача внешних данных:
useForm({
resolver: yupResolver(schema),
context: {
minAge: 18
}
});
Использование:
const schema = yup.object({
age: yup.number().test(
"min-age",
"Возраст слишком маленький",
function(value) {
return value >= this.options.context.minAge;
}
)
});
Yup умеет преобразовывать данные до возврата
результата.
yup.string().trim()
yup.string().lowercase()
const schema = yup.object({
price: yup.number().transform((value, originalValue) => {
return Number(originalValue.replace(",", "."));
})
});
yup.string().nullable()
Допускается:
null
yup.string().required()
yup.string().optional()
const schema = yup.object({
startDate: yup.date().required(),
endDate: yup
.date()
.min(
yup.ref("startDate"),
"Дата окончания меньше даты начала"
)
});
yup.number()
.min(0)
.max(100)
.integer()
.positive()
yup.string()
.min(2)
.max(30)
.matches(/^[A-Za-z]+$/)
yup.string().email()
yup.string().url()
const schema = yup.object({
password: yup
.string()
.min(8)
.matches(/[A-Z]/, "Нужна заглавная буква")
.matches(/[0-9]/, "Нужна цифра")
.matches(/[!@#$%^&*]/, "Нужен спецсимвол")
});
const schema = yup.object({
password: yup.string().required(),
confirmPassword: yup
.string()
.oneOf(
[yup.ref("password")],
"Пароли не совпадают"
)
});
Динамические схемы:
const schema = yup.lazy(value => {
if (typeof value === "string") {
return yup.string();
}
return yup.object();
});
const baseSchema = yup.object({
email: yup.string().required()
});
const profileSchema = yup.object({
age: yup.number().required()
});
const schema = baseSchema.concat(profileSchema);
Иногда требуется собственная логика.
const customResolver = async data => {
try {
const values = await schema.validate(data, {
abortEarly: false
});
return {
values,
errors: {}
};
} catch (error) {
return {
values: {},
errors: error.inner.reduce((allErrors, currentError) => {
return {
...allErrors,
[currentError.path]: {
type: currentError.type ?? "validation",
message: currentError.message
}
};
}, {})
};
}
};
Типизация формы:
import * as yup from "yup";
const schema = yup.object({
email: yup.string().required(),
age: yup.number().required()
});
type FormData = yup.InferType<typeof schema>;
Использование:
const {
register
} = useForm<FormData>({
resolver: yupResolver(schema)
});
useForm({
resolver: yupResolver(schema),
defaultValues: {
email: "",
age: 18
}
});
const {
reset
} = useForm({
resolver: yupResolver(schema)
});
reset({
email: "",
password: ""
});
const {
trigger
} = useForm({
resolver: yupResolver(schema)
});
await trigger();
Проверка конкретного поля:
await trigger("email");
const {
setError
} = useForm();
Пример:
setError("email", {
type: "server",
message: "Email уже существует"
});
clearErrors();
Для одного поля:
clearErrors("email");
const schema = yup.object({
agree: yup
.boolean()
.oneOf([true], "Необходимо согласие")
});
const schema = yup.object({
country: yup.string().required()
});
const schema = yup.object({
role: yup.string().oneOf([
"admin",
"user",
"moderator"
])
});
yup.number()
.nullable()
.transform((value, originalValue) => {
return originalValue === ""
? null
: value;
});
const schema = yup.object({
avatar: yup
.mixed()
.test(
"fileSize",
"Файл слишком большой",
value => {
if (!value?.length) {
return true;
}
return value[0].size <= 1024 * 1024;
}
)
});
Глобальная настройка:
yup.setLocale({
mixed: {
required: "Поле обязательно"
},
string: {
email: "Некорректный email"
}
});
Основные причины деградации производительности:
onChange;Плохо:
function App() {
const schema = yup.object({
email: yup.string().required()
});
}
Хорошо:
const schema = yup.object({
email: yup.string().required()
});
function App() {
}
const schema = useMemo(() => {
return yup.object({
email: yup.string().required()
});
}, []);
const authSchema = yup.object({
email: yup.string().required()
});
const profileSchema = yup.object({
age: yup.number().required()
});
Неправильно:
import yupResolver from "@hookform/resolvers/yup";
Правильно:
import { yupResolver } from "@hookform/resolvers/yup";
useForm({
resolver: yupResolver(schema)
});
Плохо:
register("user.name")
yup.object({
username: yup.string()
})
Хорошо:
yup.object({
user: yup.object({
name: yup.string()
})
})
| Resolver | Библиотека | Особенности |
|---|---|---|
| yupResolver | Yup | Простота и популярность |
| zodResolver | Zod | Отличная TypeScript-интеграция |
| joiResolver | Joi | Мощная серверная валидация |
| ajvResolver | AJV | JSON Schema |
| vestResolver | Vest | Unit-test подход |
Наиболее удачные сценарии:
Yup.Zod часто оказывается удобнее при:
AJV лучше подходит:
Joi чаще используется: