Числовые значения в валидационных схемах на основе Yup требуют строгого контроля типов, поскольку данные, поступающие из пользовательского интерфейса, почти всегда представлены строками. Основная задача слоя YupResolver заключается в согласовании входных значений формы с типами, ожидаемыми схемой, и обеспечении предсказуемого поведения при преобразовании и проверке чисел.
Для описания числового значения используется примитив
yup.number(). Этот тип задаёт основу для всех дальнейших
ограничений и преобразований.
Основные характеристики:
Типичная схема начинается с базового определения:
import * as yup from "yup";
const schema = yup.object({
age: yup.number()
});
Такое определение уже включает базовую проверку на числовой тип, однако не решает проблему строкового ввода.
В контексте HTML-форм значения полей типа
<input type="text"> всегда приходят как строки. Даже
при использовании type="number" браузер возвращает
строковое представление числа.
Yup применяет встроенное преобразование:
"42" преобразуется в 42;"" рассматривается как
NaN;NaN в
зависимости от конфигурации.Для управления поведением преобразования применяется метод
transform:
age: yup
.number()
.transform((value, originalValue) => {
return originalValue === "" ? undefined : value;
})
Такой подход позволяет избежать ситуации, когда пустое поле
интерпретируется как 0 или NaN.
Числовые поля часто должны быть обязательными. В Yup это задаётся
через required().
age: yup
.number()
.required()
Однако при работе с формами возникает особенность: 0
является валидным числом, но может быть ошибочно воспринят как пустое
значение в пользовательской логике. Поэтому различие между
undefined, null и 0 становится
критическим.
Для точного контроля используется комбинация:
age: yup
.number()
.typeError("Значение должно быть числом")
.required("Поле обязательно")
typeError позволяет отделить ошибку типа от ошибки
обязательности.
В Yup некорректные числовые значения часто приводят к
NaN. Поведение NaN отличается от стандартных
чисел:
NaN не проходит проверки min,
max, positive;NaN не равен самому себе;typeError или
transform.Распространённый паттерн очистки:
age: yup
.number()
.transform((value, originalValue) => {
const parsed = Number(originalValue);
return isNaN(parsed) ? undefined : parsed;
})
Такая трансформация позволяет централизовать контроль входных данных до этапа валидации.
Числовые значения часто ограничиваются диапазоном допустимых
значений. Yup предоставляет методы min() и
max().
age: yup
.number()
.min(18, "Минимальный возраст 18")
.max(65, "Максимальный возраст 65")
Особенности поведения:
min и max применяются только к валидным
числам;NaN автоматически считается невалидным;Для более сложных ограничений диапазон может быть динамическим:
age: yup
.number()
.min(yup.ref("minAge"))
Для ограничения числа целыми значениями используется метод
integer():
quantity: yup
.number()
.integer("Допустимы только целые значения")
Механика проверки основана на сравнении с математическим округлением:
10 — валидно;10.5 — невалидно;"10" — проходит преобразование и становится
валидным;"10.5" — зависит от парсинга и может быть
отклонено.Часто комбинируется с диапазоном:
quantity: yup
.number()
.integer()
.min(1)
.max(100)
Yup предоставляет специализированные методы:
positive() — строго больше 0;negative() — строго меньше 0;nonPositive() — меньше или равно 0;nonNegative() — больше или равно 0.Пример:
balance: yup
.number()
.nonNegative("Значение не может быть отрицательным")
Такая модель полезна для финансовых данных, где отрицательные значения недопустимы.
Числовые поля часто допускают отсутствие значения. В Yup это решается
через nullable() и default().
age: yup
.number()
.nullable()
.default(null)
Различие между undefined и null
критично:
undefined обычно означает отсутствие поля;null — явное пустое значение.При интеграции с формами это влияет на поведение reset и
начальных значений.
В связке с react-hook-form используется
yupResolver, который преобразует Yup-схему в валидатор
формы.
Базовая интеграция:
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
const schema = yup.object({
age: yup.number().required().min(18)
});
const form = useForm({
resolver: yupResolver(schema)
});
Механизм работы:
Числовые поля требуют особого внимания к valueAsNumber и
строковому вводу, поскольку несоответствие типов может приводить к
скрытым ошибкам.
Пустая строка является ключевым источником некорректной интерпретации чисел. Без трансформации:
"" → NaN;NaN → ошибка типа.Распространённая стратегия:
age: yup
.number()
.transform((value, originalValue) =>
originalValue === "" ? undefined : value
)
.required()
Такая логика позволяет отделить отсутствие данных от некорректного ввода.
Для сложных сценариев применяется test():
price: yup
.number()
.test(
"is-valid-price",
"Недопустимое значение цены",
(value) => value >= 0 && Number.isFinite(value)
)
Кастомные проверки позволяют:
Числа часто находятся внутри массивов или объектов:
schema = yup.object({
items: yup.array().of(
yup.object({
price: yup.number().min(0),
quantity: yup.number().integer().min(1)
})
)
});
При работе через YupResolver такие структуры валидируются рекурсивно, что обеспечивает согласованность данных на всех уровнях вложенности.
При частичном обновлении формы числовые поля могут находиться в промежуточном состоянии:
"1", затем "10";Это требует устойчивых схем, не зависящих от промежуточных строковых состояний.
Для повышения предсказуемости используются следующие приёмы:
typeError вместо общих сообщений;test() при простых
условиях;integer, positive,
min, max.Такая структура снижает вероятность расхождений между UI и схемой валидации.
При использовании TypeScript Yup схемы могут выводить типы автоматически, однако числовые поля требуют аккуратного определения:
number | undefined при отсутствии
required;number при обязательных значениях;number | null при nullable().Несоответствие между схемой и формой приводит к ошибкам вывода типов и некорректной автокомплитации в IDE.