При работе с формами и API данные далеко не всегда приходят в полном
и корректном виде. Одни поля могут отсутствовать полностью, другие —
содержать null, третьи — пустые строки. Библиотека Yup
предоставляет гибкий механизм управления такими ситуациями через
nullable- и optional-схемы.
Понимание различий между:
undefinednull""имеет критическое значение для корректной валидации.
undefined, null и отсутствующим
полемundefinedЗначение существует, но не определено:
const data = {
age: undefined
}
nullЯвное отсутствие значения:
const data = {
age: null
}
Свойства вообще нет:
const data = {}
По умолчанию большинство схем Yup:
undefinednullПример:
import * as yup from 'yup'
const schema = yup.string()
await schema.isValid(undefined) // true
await schema.isValid(null) // false
nullable()Метод nullable() разрешает значение
null.
const schema = yup.string().nullable()
await schema.isValid(null) // true
Без nullable():
const schema = yup.string()
await schema.isValid(null) // false
nullable()Метод влияет только на null.
| Значение | До nullable | После nullable |
|---|---|---|
"text" |
valid | valid |
undefined |
valid | valid |
null |
invalid | valid |
const schema = yup.string().nullable()
const schema = yup.number().nullable()
const schema = yup.boolean().nullable()
const schema = yup.date().nullable()
const schema = yup.array().nullable()
const schema = yup.object().nullable()
Очень частая ситуация — пустое значение select-компонента.
Например:
{
country: null
}
Без nullable() Yup выдаст ошибку:
const schema = yup.object({
country: yup.string()
})
Ошибка:
country must be a `string` type
Правильный вариант:
const schema = yup.object({
country: yup.string().nullable()
})
optional()Метод optional() делает поле необязательным.
const schema = yup.string().optional()
Теперь значение может отсутствовать:
await schema.isValid(undefined) // true
optional()Для большинства схем Yup поле уже является optional по умолчанию.
То есть:
yup.string()
и
yup.string().optional()
в большинстве случаев эквивалентны.
optional() действительно полезенГлавная задача optional() — отмена
required().
Пример:
const schema = yup.string().required().optional()
Результат:
await schema.isValid(undefined) // true
defined()Противоположность optional — defined().
Поле обязано существовать и не может быть undefined.
const schema = yup.string().defined()
Проверка:
await schema.isValid(undefined) // false
defined() и required()defined()Запрещает только undefined.
const schema = yup.string().defined()
Допустимо:
''
null // если nullable()
required()Более строгая проверка.
Для string-схем:
undefinednullconst schema = yup.string().required()
| Метод | undefined | null | “” |
|---|---|---|---|
string() |
valid | invalid | valid |
defined() |
invalid | invalid | valid |
nullable() |
valid | valid | valid |
required() |
invalid | invalid | invalid |
nullable() и required()Очень важный нюанс.
const schema = yup.string().nullable().required()
Результат:
await schema.isValid(null) // false
Почему?
Потому что required() переопределяет
nullable-поведение.
Частая комбинация:
const schema = yup.string().nullable().notRequired()
Допустимые значения:
undefined
null
'text'
''
notRequired()Альтернатива optional().
const schema = yup.string().notRequired()
Фактически:
optional()
и
notRequired()
обычно работают одинаково.
HTML input часто отправляет:
''
Но number-схема ожидает число.
Пример ошибки:
const schema = yup.number()
await schema.isValid('') // false
Типичное решение:
const schema = yup
.number()
.nullable()
.transform((value, originalValue) => {
return originalValue === '' ? null : value
})
Теперь:
await schema.isValid('') // true
Yup не преобразует пустую строку в null
автоматически.
Без transform:
''
не станет:
null
Дата — ещё один распространённый кейс.
const schema = yup.date().nullable()
Для поддержки пустого input:
const schema = yup
.date()
.nullable()
.transform((curr, orig) => {
return orig === '' ? null : curr
})
const schema = yup.object({
name: yup.string().required(),
middleName: yup.string().optional()
})
Допустимо:
{
name: 'Alex'
}
const schema = yup.object({
name: yup.string().required(),
middleName: yup.string().nullable()
})
Допустимо:
{
name: 'Alex',
middleName: null
}
Это разные концепции.
Поле можно не передавать вообще.
{}
Поле должно существовать, но может быть null.
{
field: null
}
const schema = yup.object({
field: yup.string().nullable().optional()
})
Допустимы:
{}
{
field: undefined
}
{
field: null
}
{
field: 'text'
}
default()Nullable-логика тесно связана с default-значениями.
const schema = yup.string().default('Unknown')
schema.cast(undefined)
Результат:
'Unknown'
Важно:
schema.cast(null)
не использует default.
default() применяется только к
undefined.
const schema = yup
.string()
.nullable()
.default('Anonymous')
Результаты:
| Значение | Результат |
|---|---|
undefined |
"Anonymous" |
null |
null |
const schema = yup.array().nullable()
Допустимо:
null
const schema = yup.array(
yup.string().nullable()
)
Допустимо:
['a', null, 'b']
const schema = yup.object({
name: yup.string()
}).nullable()
Допустимо:
null
В strict-режиме Yup отключает автоматические преобразования.
const schema = yup
.number()
.strict()
.nullable()
Теперь строка:
'42'
не преобразуется в число.
Yup умеет корректно выводить nullable-типы.
const schema = yup.string().nullable()
Тип:
string | null | undefined
type User = yup.InferType<typeof schema>
const userSchema = yup.object({
id: yup.number().required(),
nickname: yup
.string()
.nullable()
.optional(),
age: yup
.number()
.nullable()
.transform((v, o) => {
return o === '' ? null : v
}),
birthDate: yup
.date()
.nullable(),
contacts: yup
.array(
yup.string().nullable()
)
.optional()
})
cast()cast() преобразует данные без полноценной валидации.
const schema = yup.number().nullable()
schema.cast('42')
Результат:
42
schema.cast(null)
Результат:
null
если используется nullable().
oneOfconst schema = yup
.string()
.nullable()
.oneOf(['admin', 'user', null])
Допустимо:
null
const schema = yup
.string()
.nullable()
.test(
'custom',
'Ошибка',
value => {
if (value === null) {
return true
}
return value.length > 3
}
)
Неверно:
yup.string().nullable()
Поле всё ещё может участвовать в required-проверках объекта.
''
это не:
null
yup.string().nullable().required()
null всё равно будет запрещён.
Наиболее распространённая схема для необязательных полей формы:
yup
.string()
.nullable()
.transform(value => {
return value === '' ? null : value
})
yup
.number()
.nullable()
.transform((value, originalValue) => {
return originalValue === ''
? null
: value
})
yup
.date()
.nullable()
.transform((value, originalValue) => {
return originalValue === ''
? null
: value
})
| Комбинация | undefined | null | “” |
|---|---|---|---|
string() |
valid | invalid | valid |
string().nullable() |
valid | valid | valid |
string().required() |
invalid | invalid | invalid |
string().nullable().optional() |
valid | valid | valid |
number().nullable() |
valid | valid | invalid |
number().nullable()+transform |
valid | valid | valid |
date().nullable() |
valid | valid | invalid |
date().nullable()+transform |
valid | valid | valid |