Min и max для строк и чисел

Валидация диапазонов значений является одной из базовых задач при работе с формами. В контексте YupResolver, который выступает адаптером между схемами валидации Yup и библиотеками управления формами (например, React Hook Form), механизмы min и max используются для строгого контроля допустимых значений как для строк, так и для числовых данных.


Принцип работы min и max в Yup

В Yup ограничения min и max применяются к разным типам данных по-разному:

  • для строк — определяют минимальную и максимальную длину;
  • для чисел — задают допустимый диапазон значений;
  • для массивов — ограничивают количество элементов (в рамках расширенного использования).

YupResolver не изменяет логику Yup, а лишь передаёт схему валидации, поэтому поведение строго соответствует Yup API.


Ограничение длины строк

При работе со строками min и max определяют количество символов в значении.

Базовый пример строки с ограничениями

import * as Yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";

const schema = Yup.object({
  username: Yup.string()
    .min(3, "Минимальная длина — 3 символа")
    .max(20, "Максимальная длина — 20 символов")
    .required("Поле обязательно для заполнения"),
});

В данном случае:

  • значение короче 3 символов считается невалидным;
  • значение длиннее 20 символов отклоняется;
  • пустое значение блокируется через required.

Особенности работы min/max со строками

При использовании строк важно учитывать следующие особенности:

1. Подсчёт символов, а не байтов

Yup работает с количеством символов, а не с байтовым представлением строки. Это особенно важно при использовании Unicode-символов, включая кириллицу и эмодзи.

2. Пробелы учитываются

Пробелы внутри строки считаются частью длины:

Yup.string().min(5);

Строка "a b c" считается длиной 5 символов.

3. Пустая строка

Пустая строка "" не проходит проверку min, если не указано nullable или не изменена логика обработки значения.


Использование transform для нормализации строк перед проверкой

Часто требуется предварительно обрезать пробелы:

const schema = Yup.object({
  email: Yup.string()
    .transform(value => value?.trim())
    .min(5)
    .max(50)
    .required(),
});

В этом случае длина считается после удаления пробелов по краям, что уменьшает количество ложных ошибок валидации.


Ограничение числовых значений

Для чисел min и max задают допустимый диапазон значений.

Пример числовой валидации

const schema = Yup.object({
  age: Yup.number()
    .min(18, "Минимальный возраст — 18 лет")
    .max(65, "Максимальный возраст — 65 лет")
    .required("Возраст обязателен"),
});

Здесь значение должно находиться в диапазоне от 18 до 65 включительно.


Важные особенности числовых min/max

1. Типизация входных данных

Yup строго различает строки и числа. Значение "25" (строка) не будет автоматически считаться числом без преобразования.

Для корректной работы часто используется transform или typeError:

Yup.number()
  .typeError("Введите число")
  .min(10)
  .max(100);

2. NaN и пустые значения

Если значение не может быть преобразовано в число, Yup возвращает NaN, что приводит к ошибке валидации.


3. Включительность границ

min и max являются включительными:

  • min(10) допускает значение 10;
  • max(20) допускает значение 20.

Сочетание min/max с другими правилами

На практике ограничения часто комбинируются с другими методами Yup.

Пример комбинированной схемы

const schema = Yup.object({
  password: Yup.string()
    .min(8)
    .max(32)
    .matches(/[A-Z]/, "Должна быть хотя бы одна заглавная буква")
    .matches(/[0-9]/, "Должна быть хотя бы одна цифра")
    .required(),
});

Здесь min и max задают базовый диапазон длины, а matches добавляет структурные ограничения.


Поведение min/max при nullable и optional значениях

При использовании nullable и notRequired поведение меняется:

Yup.string()
  .min(3)
  .max(10)
  .nullable()
  .notRequired();
  • null не проходит проверку длины, если не настроено иначе;
  • отсутствие значения игнорирует проверку;
  • пустая строка остаётся валидируемым значением.

Работа с динамическими ограничениями

Yup позволяет задавать динамические значения min/max через функции:

const schema = Yup.object({
  password: Yup.string().min(
    8,
    ({ min }) => `Минимум ${min} символов`
  ),
});

Также можно использовать внешние переменные:

const MIN_LENGTH = 5;
const MAX_LENGTH = 15;

Yup.string()
  .min(MIN_LENGTH)
  .max(MAX_LENGTH);

Валидация диапазонов в числах с зависимыми полями

Часто min/max зависит от другого поля формы:

const schema = Yup.object({
  minValue: Yup.number().required(),
  maxValue: Yup.number()
    .min(Yup.ref("minValue"), "Не может быть меньше минимального значения")
});

В этом случае min использует ссылку на другое поле формы, обеспечивая согласованность данных.


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

При использовании YupResolver схема передаётся в resolver без изменений:

import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";

const {
  register,
  handleSubmit,
  formState: { errors }
} = useForm({
  resolver: yupResolver(schema),
});

Ошибки, возвращаемые min и max, автоматически попадают в errors и могут быть отображены в интерфейсе.


Типичные ошибки при использовании min и max

1. Отсутствие преобразования типов

Числа приходят как строки из input:

Yup.number().min(10);

без valueAsNumber в форме приводит к некорректной валидации.


2. Игнорирование пустых значений

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


3. Неправильная интерпретация длины строк

Emoji и сложные Unicode-символы могут восприниматься как один символ, хотя визуально занимают больше места.


Поведение с массивами (расширенное применение)

Хотя основной фокус min и max — строки и числа, для массивов они работают как ограничение длины:

Yup.array()
  .min(1, "Нужно выбрать хотя бы один элемент")
  .max(5, "Максимум 5 элементов");

Это важно при работе с чекбоксами и множественным выбором.


Итоговая логика применения min и max в YupResolver

  • min и max являются декларативными ограничителями диапазона;
  • работают одинаково стабильно через YupResolver;
  • различаются по поведению в зависимости от типа данных;
  • комбинируются с трансформациями и зависимыми полями;
  • обеспечивают основу структурной валидации формовых данных.