Ограничение размера файла

Ограничение размера файла в схемах валидации на базе Yup реализуется через кастомные проверки, поскольку стандартные примитивы библиотеки ориентированы на строковые, числовые и логические типы. При работе с загрузкой файлов в веб-приложениях значение поля обычно представлено объектом File или массивом FileList, где ключевым параметром становится свойство size, выраженное в байтах.

Объект File в браузере содержит метаданные, среди которых:

  • name — имя файла
  • type — MIME-тип
  • size — размер в байтах
  • lastModified — дата последнего изменения

Именно size используется для ограничения объёма загружаемого файла.

В Yup нет встроенного метода вроде maxFileSize, поэтому используется расширение через mixed().test().

import * as yup fr om "yup";

const schema = yup.object({
  file: yup
    .mixed()
    .required("Файл обязателен")
    .test("fileSize", "Файл слишком большой", (value) => {
      if (!value) return false;
      return value.size <= 5 * 1024 * 1024;
    }),
});

В данном примере ограничение установлено на 5 MB. Проверка выполняется напрямую через доступ к свойству size.

Интеграция с react-hook-form через YupResolver

При использовании @hookform/resolvers/yup схема подключается как единый валидатор формы.

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

const schema = yup.object({
  file: yup
    .mixed()
    .required("Файл обязателен")
    .test("fileSize", "Максимальный размер 2MB", (value) => {
      return value && value.size <= 2 * 1024 * 1024;
    }),
});

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

  const onSub mit = (data) => {
    console.log(data.file);
  };

  return (
    <form onSub mit={handleSubmit(onSubmit)}>
      <input type="file" {...register("file")} />
      {errors.file && <p>{errors.file.message}</p>}
      <button type="submit">Отправить</button>
    </form>
  );
}

В этом сценарии YupResolver преобразует результат схемы Yup в формат ошибок, понятный react-hook-form, обеспечивая единый поток валидации.

Обработка FileList и множественной загрузки

При множественной загрузке input[type="file"] возвращает FileList, который не является массивом, но может быть обработан как итерируемая структура. В этом случае проверка размера должна учитывать каждый файл отдельно.

const schema = yup.object({
  files: yup
    .mixed()
    .test("fileSize", "Один из файлов превышает лимит 3MB", (value) => {
      if (!value) return false;

      const filesArray = Array.from(value);
      return filesArray.every((file) => file.size <= 3 * 1024 * 1024);
    }),
});

Здесь используется преобразование Array.from, позволяющее применять стандартные методы массива.

Разделение логики проверки и повторное использование

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

const maxFileSize = (lim it) =>
  yup
    .mixed()
    .test(
      "fileSize",
      `Файл превышает ${lim it / (1024 * 1024)}MB`,
      (value) => {
        if (!value) return false;
        return value.size <= lim it;
      }
    );

const schema = yup.object({
  avatar: maxFileSize(1 * 1024 * 1024),
  document: maxFileSize(10 * 1024 * 1024),
});

Такой подход упрощает сопровождение и обеспечивает единообразие правил валидации.

Учет необязательных файловых полей

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

const schema = yup.object({
  file: yup
    .mixed()
    .test("fileSize", "Файл слишком большой", (value) => {
      if (!value) return true;
      return value.size <= 5 * 1024 * 1024;
    }),
});

Ключевой момент заключается в том, что отсутствие файла считается допустимым состоянием.

Комбинация с проверкой типа файла

Ограничение размера часто применяется вместе с проверкой MIME-типа, что позволяет формировать более строгие правила загрузки.

const schema = yup.object({
  file: yup
    .mixed()
    .test("fileType", "Неподдерживаемый формат", (value) => {
      if (!value) return false;
      return ["image/jpeg", "image/png"].includes(value.type);
    })
    .test("fileSize", "Файл больше 4MB", (value) => {
      if (!value) return false;
      return value.size <= 4 * 1024 * 1024;
    }),
});

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

Особенности работы с серверной валидацией

Хотя YupResolver обеспечивает клиентскую проверку, ограничения размера файла должны дублироваться на сервере. Причина заключается в том, что клиентская валидация не является защитным механизмом, а лишь улучшает пользовательский опыт.

На серверной стороне аналогичная проверка обычно реализуется через middleware или встроенные ограничения загрузчика (например, multer в Node.js):

const upload = multer({
  limits: {
    fileSize: 5 * 1024 * 1024,
  },
});

Согласованность лимитов между клиентом и сервером предотвращает расхождения в поведении системы.

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

Yup позволяет гибко формировать сообщения об ошибках в зависимости от контекста. Это особенно важно при работе с файлами разного назначения.

const schema = yup.object({
  file: yup
    .mixed()
    .test("fileSize", (value) => {
      if (!value) return "Файл не выбран";
      if (value.size > 2 * 1024 * 1024) {
        return "Размер файла не должен превышать 2MB";
      }
      return true;
    }),
});

Такой вариант позволяет динамически возвращать строку ошибки вместо статического сообщения.

Обработка drag-and-drop загрузки

При использовании drag-and-drop компонентов структура данных остаётся аналогичной FileList, но часто дополнительно нормализуется до массива файлов. В этом случае проверка размера становится частью более широкой схемы нормализации данных.

const schema = yup.object({
  files: yup
    .array()
    .of(
      yup.mixed().test("fileSize", "Файл превышает лимит", (file) => {
        return file && file.size <= 3 * 1024 * 1024;
      })
    ),
});

Такая структура обеспечивает предсказуемость при работе с кастомными загрузчиками.

Оптимизация проверок при больших формах

При большом количестве файловых полей имеет смысл минимизировать повторяющиеся вычисления лимитов и использовать константы:

const LIMIT_5MB = 5 * 1024 * 1024;

const schema = yup.object({
  avatar: yup.mixed().test("fileSize", "Лимит 5MB", (file) => {
    return file ? file.size <= LIMIT_5MB : false;
  }),
  attachment: yup.mixed().test("fileSize", "Лимит 5MB", (file) => {
    return file ? file.size <= LIMIT_5MB : false;
  }),
});

Это снижает вероятность расхождений между полями и упрощает поддержку.

Поведение при асинхронной загрузке

Хотя Yup поддерживает асинхронные проверки через test().then, для проверки размера файла это не требуется, поскольку операция является синхронной и не зависит от внешних источников данных. Однако архитектурно важно учитывать, что YupResolver может обрабатывать асинхронные схемы, что позволяет расширять логику при необходимости введения удалённых проверок, например анализа файла перед загрузкой.