Числа: number schema

Числовая схема в Yup строится вокруг базового валидатора, который обеспечивает проверку, приведение и ограничение значений типа number. Основой служит вызов yup.number(), возвращающий схему, предназначенную для работы с числовыми данными в формах, API-ответах и любых структурах, где требуется строгая типизация значений.

Внутренне схема числа работает в двух режимах: приведение (casting) и валидация. Приведение отвечает за преобразование входного значения в число, если это возможно, а валидация — за проверку соответствия заданным правилам. Это разделение важно, поскольку входные данные часто приходят в виде строк, особенно в контексте HTML-форм.

import * as yup from 'yup';

const schema = yup.number();

Такой вариант допускает любые числовые значения, включая NaN, Infinity, -Infinity, если не заданы дополнительные ограничения. Это важно учитывать при проектировании схем, так как Yup по умолчанию не ограничивает математический диапазон.

Приведение типов и особенности преобразования

Числовая схема активно использует преобразование типов. Значения типа string, содержащие числа, автоматически приводятся:

yup.number().validateSync("42"); // 42
yup.number().validateSync("3.14"); // 3.14

Пустые строки при этом преобразуются в NaN, что часто требует дополнительной обработки:

yup.number().nullable().transform((value, originalValue) => {
  return originalValue === "" ? null : value;
});

Функция transform позволяет полностью контролировать процесс преобразования входных данных до этапа валидации. Это особенно важно при работе с формами, где пустые значения часто представлены строками.

Обязательность значения

Для указания обязательности используется метод required:

yup.number().required();

Отсутствие значения или undefined приведёт к ошибке валидации. Однако null по умолчанию не считается допустимым значением, если не указано явно:

yup.number().nullable().required();

Комбинация nullable и required позволяет различать отсутствие значения и его явное обнуление.

Ограничения диапазона

Для чисел часто применяются ограничения диапазона:

yup.number().min(0);
yup.number().max(100);

Метод min задаёт нижнюю границу включительно, max — верхнюю. Эти ограничения работают после приведения типов, что означает, что строковые значения сначала преобразуются в числа.

Также существуют строгие варианты:

yup.number().moreThan(0);  // строго больше
yup.number().lessThan(100); // строго меньше

Эти методы полезны при исключении граничных значений, например в процентах или коэффициентах.

Проверка знака числа

Часто требуется ограничить знак числа:

yup.number().positive();
yup.number().negative();
yup.number().nonNegative();
yup.number().nonPositive();

positive исключает ноль, тогда как nonNegative допускает его. Аналогично работает пара negative и nonPositive.

Целые числа

Для проверки целочисленного значения используется метод:

yup.number().integer();

Он исключает дробные значения. При этом значения вида "10.0" после приведения могут считаться валидными или невалидными в зависимости от результата кастинга.

Обработка NaN и бесконечностей

По умолчанию NaN считается невалидным значением. Это поведение можно контролировать через трансформации:

yup.number().transform((value, originalValue) => {
  return isNaN(value) ? undefined : value;
});

Infinity и -Infinity также считаются числами JavaScript, но часто исключаются бизнес-логикой через кастомные тесты:

yup.number().test(
  'finite',
  'Значение должно быть конечным числом',
  (value) => Number.isFinite(value)
);

Кастомные проверки (test)

Механизм test позволяет добавлять произвольную логику:

yup.number().test(
  'is-even',
  'Число должно быть чётным',
  (value) => value % 2 === 0
);

Каждый тест получает значение после преобразования и может возвращать true или false. Также допускается асинхронная проверка:

yup.number().test(
  'async-check',
  'Недопустимое значение',
  async (value) => {
    const result = await someApiCheck(value);
    return result.valid;
  }
);

Значения по умолчанию

Схема может задавать значение по умолчанию:

yup.number().default(0);

Это значение применяется при отсутствии входных данных. Важно, что default не заменяет null, если не используется дополнительная логика трансформации.

Строгий режим

Режим strict отключает автоматическое приведение типов:

yup.number().strict();

В этом случае строка "42" не будет преобразована в число и вызовет ошибку. Это используется в системах, где требуется строгий контроль входных данных без неявных преобразований.

Сравнение и зависимые правила

Числовая схема может зависеть от других полей через when:

yup.number().when('minValue', (minValue, schema) => {
  return schema.min(minValue);
});

Такой подход позволяет строить динамические ограничения, зависящие от контекста формы или состояния данных.

Работа с локалью и разделителями

При кастинге строк в числа учитываются особенности JavaScript Number(), который не поддерживает локализованные форматы:

yup.number().validateSync("1,5"); // NaN

Для поддержки локалей требуется предварительная трансформация:

yup.number().transform((value, originalValue) => {
  if (typeof originalValue === 'string') {
    return parseFloat(originalValue.replace(',', '.'));
  }
  return value;
});

Комбинирование правил

Числовая схема часто представляет цепочку ограничений:

yup.number()
  .required()
  .integer()
  .min(1)
  .max(100)
  .positive();

Каждое правило применяется последовательно, и ошибка возникает на первом нарушенном условии, если не настроено накопление ошибок.

Порядок выполнения проверок

В Yup сначала выполняется трансформация, затем кастинг, после чего идут синхронные и асинхронные тесты. Это означает, что изменение входного значения через transform влияет на все последующие этапы.

yup.number()
  .transform((value) => value * 2)
  .min(10);

В данном случае проверка min(10) применяется уже к преобразованному значению.

Ошибки и кастомизация сообщений

Каждый метод поддерживает кастомные сообщения:

yup.number().min(10, 'Минимальное значение — 10');

Также сообщения могут формироваться через функции:

yup.number().max(100, ({ max }) => `Максимум: ${max}`);

Это позволяет адаптировать текст ошибок под контекст интерфейса.

Типизация в TypeScript

Числовая схема интегрируется с системой типов:

const schema: yup.NumberSchema<number | undefined> = yup.number();

Это позволяет выводить тип результата в зависимости от настроек required, nullable и default.

Особенно важно учитывать, что результат валидации может отличаться от входного типа из-за кастинга.

Особенности поведения при отсутствии значения

Разные комбинации методов дают различное поведение:

  • number() — допускает undefined
  • required() — исключает undefined
  • nullable() — допускает null
  • default() — подставляет значение при отсутствии

Эти параметры формируют базовую модель допустимых состояний числа и требуют согласованной настройки при проектировании схемы данных.