Валидация содержимого файлов

Валидация файлов в схемах, построенных на основе Yup, опирается на обработку объектов типа File и FileList, которые предоставляются браузерным API. При использовании резолвера React Hook Form через yupResolver такие данные проходят через единый слой преобразования и проверки, где схема становится источником строгих правил для загружаемого содержимого.

Браузерные файлы представлены двумя основными структурами:

  • File — единичный файл с метаданными
  • FileList — коллекция файлов, получаемая через <input type="file" />

Объект File содержит ключевые свойства:

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

При валидации через Yup важно учитывать, что FileList не является массивом в полном смысле, поэтому часто требуется приведение к массиву:

const filesArray = (value) => {
  if (!value) return [];
  return Array.from(value);
};

Базовая схема проверки файла

Простейшая схема в Yup может проверять наличие файла:

import * as Yup from "yup";

const schema = Yup.object({
  file: Yup.mixed().required("Файл обязателен"),
});

Тип mixed() используется потому, что файл не относится к примитивным типам.

Проверка размера файла

Одним из ключевых ограничений является контроль размера:

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

Логика проверки строится вокруг свойства size, измеряемого в байтах. Ограничение в 5 MB часто используется как базовый порог для изображений и документов.

Проверка MIME-типа

Контроль формата файла реализуется через type:

const schema = Yup.object({
  file: Yup.mixed()
    .required("Файл обязателен")
    .test("fileType", "Неподдерживаемый формат", (value) => {
      if (!value) return false;
      return ["image/jpeg", "image/png", "application/pdf"].includes(value.type);
    }),
});

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

Валидация нескольких файлов

При работе с множественной загрузкой применяется массив файлов:

const schema = Yup.object({
  files: Yup.array()
    .of(Yup.mixed())
    .min(1, "Необходимо загрузить хотя бы один файл")
    .max(5, "Максимум 5 файлов"),
});

Дополнительно можно валидировать каждый элемент массива:

const fileSchema = Yup.mixed()
  .test("fileSize", "Слишком большой файл", (file) => {
    return file && file.size <= 3 * 1024 * 1024;
  });

const schema = Yup.object({
  files: Yup.array().of(fileSchema),
});

Кастомные проверки через test

Метод test является основным механизмом расширения логики:

const schema = Yup.object({
  file: Yup.mixed().test(
    "is-image",
    "Только изображения",
    (value) => {
      return value && value.type.startsWith("image/");
    }
  ),
});

В рамках test доступен полный контроль над объектом файла, включая комбинацию нескольких условий.

Проверка разрешения изображений

Для графических файлов часто требуется проверка размеров изображения:

const validateImageDimensions = (file) =>
  new Promise((resolve) => {
    const img = new Image();
    img.src = URL.createObjectURL(file);

    img.onl oad = () => {
      const valid = img.width <= 1920 && img.height <= 1080;
      resolve(valid);
    };

    img.oner ror = () => resolve(false);
  });

Интеграция с Yup:

const schema = Yup.object({
  image: Yup.mixed().test(
    "dimensions",
    "Недопустимое разрешение",
    async (file) => {
      if (!file) return false;
      return await validateImageDimensions(file);
    }
  ),
});

Асинхронная валидация файлов

Yup поддерживает асинхронные проверки, что особенно важно для файлов:

  • проверка вирусов
  • загрузка метаданных
  • запрос к серверу для анализа содержимого

Пример асинхронной проверки:

const checkFileOnServer = async (file) => {
  const formData = new FormData();
  formData.append("file", file);

  const res = await fetch("/api/validate-file", {
    method: "POST",
    body: formData,
  });

  const data = await res.json();
  return data.valid;
};

Схема:

const schema = Yup.object({
  file: Yup.mixed().test(
    "server-validation",
    "Файл не прошёл проверку сервера",
    async (file) => {
      if (!file) return false;
      return await checkFileOnServer(file);
    }
  ),
});

Интеграция с React Hook Form и yupResolver

Связка React Hook Form и Yup позволяет централизовать управление файлами:

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

const schema = Yup.object({
  file: Yup.mixed()
    .required()
    .test("fileSize", "Слишком большой файл", (file) => {
      return file && file.size <= 2 * 1024 * 1024;
    }),
});

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

HTML-интеграция:

<input type="file" {...register("file")} />

При отправке формы файл автоматически попадает в схему и проходит все проверки.

Нормализация входных данных

Часто требуется преобразование FileList в массив:

const schema = Yup.object({
  files: Yup.mixed().transform((value) => {
    if (!value) return [];
    return Array.from(value);
  }),
});

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

Ошибки валидации файлов

Ошибки в Yup имеют стандартную структуру:

  • path — поле, где произошла ошибка
  • message — текст ошибки
  • type — тип правила

Пример:

{
  path: "file",
  message: "Файл слишком большой",
  type: "fileSize"
}

При использовании resolver эти ошибки автоматически передаются в React Hook Form и становятся доступны через formState.errors.

Комплексные сценарии валидации

Комбинированные ограничения

const schema = Yup.object({
  file: Yup.mixed()
    .required()
    .test("fileType", "Только PDF", (file) => {
      return file?.type === "application/pdf";
    })
    .test("fileSize", "Максимум 10MB", (file) => {
      return file && file.size <= 10 * 1024 * 1024;
    }),
});

Валидация архива файлов

files: Yup.array()
  .of(
    Yup.mixed()
      .test("size", "Файл слишком большой", (f) => f.size < 5e6)
      .test("type", "Недопустимый тип", (f) => f.name.endsWith(".zip"))
  )

Производственные ограничения и оптимизация

При работе с большими файлами важно учитывать:

  • избегание повторного создания URL.createObjectURL
  • очистку временных ссылок через URL.revokeObjectURL
  • минимизацию асинхронных проверок на клиенте
  • перенос тяжёлой валидации на сервер

Схемы Yup в связке с resolver используются как первый уровень фильтрации, снижая нагрузку на backend и предотвращая отправку заведомо некорректных файлов.