Механизм lazy() в библиотеке Yup предназначен для
динамического построения схемы валидации во время выполнения. В отличие
от статических схем, где структура заранее фиксирована,
lazy() позволяет выбирать правила на основе текущего
значения поля, структуры объекта или внешнего состояния.
В связке с yupResolver из React Hook Form это особенно
полезно для:
lazy()Метод принимает функцию, возвращающую схему валидации.
yup.lazy((value) => {
return schema
})
Аргумент value — текущее значение валидируемого
поля.
Пример:
import * as yup from 'yup'
const schema = yup.object({
value: yup.lazy((value) => {
if (typeof value === 'string') {
return yup.string().min(3)
}
if (typeof value === 'number') {
return yup.number().positive()
}
return yup.mixed().notRequired()
})
})
Поведение:
| Значение | Используемая схема |
|---|---|
"abc" |
string().min(3) |
15 |
number().positive() |
null |
mixed() |
Подключение к форме:
import { useForm } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import * as yup from 'yup'
const schema = yup.object({
data: yup.lazy((value) => {
return typeof value === 'string'
? yup.string().required()
: yup.number().required()
})
})
const form = useForm({
resolver: yupResolver(schema)
})
yupResolver не требует специальной настройки для
lazy(). Resolver автоматически вызывает динамическую схему
во время каждой проверки.
Частый сценарий — API может возвращать поле либо строкой, либо объектом.
const schema = yup.object({
user: yup.lazy((value) => {
if (typeof value === 'string') {
return yup.string().required()
}
return yup.object({
id: yup.number().required(),
name: yup.string().required()
})
})
})
Примеры валидных данных:
{
user: "admin"
}
{
user: {
id: 1,
name: "Alex"
}
}
lazy() особенно полезен для массивов
polymorphic-элементов.
const itemSchema = yup.lazy((value) => {
switch (value.type) {
case 'text':
return yup.object({
type: yup.string().required(),
value: yup.string().required()
})
case 'number':
return yup.object({
type: yup.string().required(),
value: yup.number().required()
})
default:
return yup.mixed().test({
name: 'invalid-type',
message: 'Unsupported type',
test: () => false
})
}
})
const schema = yup.object({
items: yup.array().of(itemSchema)
})
Валидный объект:
{
items: [
{
type: 'text',
value: 'hello'
},
{
type: 'number',
value: 100
}
]
}
const schema = yup.object({
config: yup.lazy((value) => {
if (value == null) {
return yup.mixed().notRequired()
}
return yup.object({
enabled: yup.boolean().required()
})
})
})
Особенность:
null и undefined проходят без ошибок;const schema = yup.object({
tags: yup.lazy((value) => {
if (!Array.isArray(value)) {
return yup.array()
}
if (value.length > 5) {
return yup.array().max(10)
}
return yup.array().max(5)
})
})
const addressSchema = yup.lazy((value) => {
if (value?.country === 'US') {
return yup.object({
country: yup.string().required(),
zip: yup.string().required().matches(/^\d{5}$/)
})
}
return yup.object({
country: yup.string().required(),
postalCode: yup.string().required()
})
})
const schema = yup.object({
address: addressSchema
})
const nodeSchema = yup.lazy(() => {
return yup.object({
id: yup.number().required(),
children: yup.array().of(nodeSchema)
})
})
Такая конструкция позволяет валидировать рекурсивные деревья.
Пример:
{
id: 1,
children: [
{
id: 2,
children: []
}
]
}
lazy() и
when()when() и lazy() решают похожие задачи, но
работают по-разному.
when()Используется для изменения правил существующей схемы.
yup.string().when('role', {
is: 'admin',
then: (schema) => schema.required()
})
lazy()Полностью заменяет схему.
yup.lazy((value) => {
return typeof value === 'string'
? yup.string()
: yup.number()
})
const schema = yup.object({
type: yup.string().required(),
payload: yup.lazy((value, options) => {
const type = options.parent.type
if (type === 'email') {
return yup.object({
email: yup.string().email().required()
})
}
return yup.object({
phone: yup.string().required()
})
})
})
options.parentВнутри lazy() доступен второй аргумент.
yup.lazy((value, options) => {
console.log(options)
})
Основные свойства:
| Поле | Назначение |
|---|---|
parent |
родительский объект |
context |
внешний контекст |
path |
путь до поля |
originalValue |
исходное значение |
const schema = yup.object({
mode: yup.string().required(),
settings: yup.lazy((value, options) => {
const mode = options.parent.mode
if (mode === 'advanced') {
return yup.object({
cache: yup.boolean().required(),
retries: yup.number().required()
})
}
return yup.object({
cache: yup.boolean()
})
})
})
Resolver поддерживает передачу context.
const form = useForm({
resolver: yupResolver(schema, {
context: {
isAdmin: true
}
})
})
Получение внутри lazy():
const schema = yup.object({
permissions: yup.lazy((value, options) => {
const isAdmin = options.context.isAdmin
if (isAdmin) {
return yup.array().min(1)
}
return yup.array().max(0)
})
})
const schema = yup.object({
type: yup.string().required(),
data: yup.lazy((value, options) => {
switch (options.parent.type) {
case 'login':
return yup.object({
email: yup.string().email().required(),
password: yup.string().required()
})
case 'register':
return yup.object({
username: yup.string().required(),
email: yup.string().email().required(),
password: yup.string().min(8).required()
})
default:
return yup.mixed()
}
})
})
const stepSchema = yup.lazy((value, options) => {
switch (options.context.step) {
case 1:
return yup.object({
name: yup.string().required()
})
case 2:
return yup.object({
email: yup.string().email().required()
})
case 3:
return yup.object({
password: yup.string().min(8).required()
})
default:
return yup.object()
}
})
const schema = yup.lazy((value: unknown) => {
if (typeof value === 'string') {
return yup.string()
}
return yup.number()
})
lazy() ухудшает автоматическое определение типов.
type FormData = yup.InferType<typeof schema>
В сложных схемах тип может стать:
any
или:
unknown
type Payload =
| string
| {
id: number
name: string
}
lazy() вызывается:
trigger;Сложные схемы могут создавать лишнюю нагрузку.
Плохо:
yup.lazy(() => {
return yup.object({
items: yup.array().of(
yup.object({
huge: yup.string()
})
)
})
})
На каждой проверке создаётся новая схема.
Хорошо:
const hugeSchema = yup.object({
items: yup.array().of(
yup.object({
huge: yup.string()
})
)
})
const schema = yup.lazy(() => hugeSchema)
nullable()const schema = yup.lazy((value) => {
if (value === null) {
return yup.mixed().nullable()
}
return yup.string().required()
})
const schema = yup.lazy((value) => {
if (typeof value === 'object') {
return yup.object({
enabled: yup.boolean().default(false)
})
}
return yup.string()
})
const jsonSchema = yup.lazy((value) => {
if (Array.isArray(value)) {
return yup.array().of(jsonSchema)
}
if (typeof value === 'object' && value !== null) {
return yup.object(
Object.keys(value).reduce((acc, key) => {
acc[key] = jsonSchema
return acc
}, {})
)
}
switch (typeof value) {
case 'string':
return yup.string()
case 'number':
return yup.number()
case 'boolean':
return yup.boolean()
default:
return yup.mixed()
}
})
Неправильно:
yup.lazy((value) => {
return {}
})
Правильно:
yup.lazy((value) => {
return yup.object()
})
Неправильно:
yup.lazy((value) => {
if (typeof value === 'string') {
return yup.string()
}
})
Если условие не выполнится — возникнет ошибка.
Правильно:
yup.lazy((value) => {
if (typeof value === 'string') {
return yup.string()
}
return yup.mixed()
})
lazy() не предназначен для асинхронного получения
схем.
Плохо:
yup.lazy(async () => {
return yup.string()
})
import * as yup from 'yup'
const paymentSchema = yup.object({
method: yup.string().required(),
details: yup.lazy((value, options) => {
switch (options.parent.method) {
case 'card':
return yup.object({
cardNumber: yup.string().required(),
cvv: yup.string().required()
})
case 'paypal':
return yup.object({
email: yup.string().email().required()
})
case 'crypto':
return yup.object({
wallet: yup.string().required(),
network: yup.string().required()
})
default:
return yup.mixed()
}
})
})
Варианты валидных данных:
{
method: 'card',
details: {
cardNumber: '1111 2222 3333 4444',
cvv: '123'
}
}
{
method: 'paypal',
details: {
email: 'test@mail.com'
}
}
{
method: 'crypto',
details: {
wallet: '0x123',
network: 'ETH'
}
}
const createAdminSchema = () => {
return yup.object({
role: yup.string().required()
})
}
const createUserSchema = () => {
return yup.object({
name: yup.string().required()
})
}
const schema = yup.lazy((value) => {
return value?.isAdmin
? createAdminSchema()
: createUserSchema()
})
schemas/
├── user.schema.js
├── payment.schema.js
├── product.schema.js
└── dynamic.schema.js
lazy()Плохо:
yup.lazy((value) => {
// 200 строк условий
})
Хорошо:
const resolveSchema = (value) => {
if (typeof value === 'string') {
return stringSchema
}
return objectSchema
}
const schema = yup.lazy(resolveSchema)
lazy() от
альтернатив| Подход | Назначение |
|---|---|
when() |
изменение правил |
test() |
кастомная проверка |
transform() |
преобразование данных |
lazy() |
динамическая замена схемы |
lazy() особенно
полезенlazy()В таких случаях when() обычно проще и читаемее.