Работа с множественными файлами в формах требует согласованной модели
данных, поскольку браузерный input[type="file"] возвращает
либо FileList, либо преобразованный массив
File[], а библиотека валидации должна корректно
интерпретировать оба варианта. При использовании
YupResolver в связке с формами на основе схемы Yup ключевая
задача заключается в унификации структуры входных данных и построении
устойчивых правил проверки.
В большинстве сценариев поле файлов в форме нормализуется к одному из двух типов:
FileList — нативный результат DOM-элемента
<input type="file" multiple />File[] — преобразованный массив для удобства работы и
валидацииДля Yup предпочтительнее второй вариант, поскольку он обеспечивает предсказуемость и поддержку методов массива.
Основой для множественных файлов является использование
array().of(mixed()):
import * as Yup from "yup";
const schema = Yup.object({
files: Yup.array()
.of(Yup.mixed())
});
Такая конструкция уже позволяет обрабатывать набор файлов, но не накладывает ограничений. В реальных формах требуется более строгая валидация.
Одним из частых требований является контроль минимального и максимального числа загружаемых файлов.
files: Yup.array()
.of(Yup.mixed())
.min(1, "Необходимо загрузить хотя бы один файл")
.max(5, "Максимум 5 файлов")
Методы min и max работают на уровне
массива, что делает их удобными для контроля UI-ограничений компонента
загрузки.
Пустой массив и отсутствие файлов требуют различной обработки. Для строгой обязательности используется проверка длины:
files: Yup.array()
.of(Yup.mixed())
.required("Файлы обязательны")
.min(1, "Загрузите хотя бы один файл")
Метод required() проверяет наличие поля, но не
гарантирует наличие элементов, поэтому min(1) является
критически важным.
При множественной загрузке часто требуется ограничить типы файлов.
Это реализуется через кастомный test:
files: Yup.array()
.of(
Yup.mixed().test(
"fileType",
"Недопустимый формат файла",
(file) => {
if (!file) return true;
const allowedTypes = ["image/jpeg", "image/png", "application/pdf"];
return allowedTypes.includes(file.type);
}
)
)
Каждый файл проходит независимую проверку, что позволяет точно контролировать содержимое массива.
Ограничение по размеру особенно важно при множественной загрузке, так как суммарный объём может быть значительным.
files: Yup.array().of(
Yup.mixed().test(
"fileSize",
"Файл слишком большой",
(file) => {
if (!file) return true;
const maxSize = 5 * 1024 * 1024;
return file.size <= maxSize;
}
)
)
При необходимости можно дополнительно вводить проверку суммарного размера массива.
В реальных схемах ограничения по количеству, типу и размеру применяются одновременно:
files: Yup.array()
.of(
Yup.mixed()
.test("fileType", "Неверный тип", (file) => {
if (!file) return true;
return ["image/png", "image/jpeg"].includes(file.type);
})
.test("fileSize", "Слишком большой файл", (file) => {
if (!file) return true;
return file.size <= 3 * 1024 * 1024;
})
)
.min(1)
.max(10)
Такая структура обеспечивает многослойную проверку без необходимости писать внешнюю логику.
При использовании YupResolver схема подключается как
единый источник правил валидации:
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
const form = useForm({
resolver: yupResolver(schema)
});
В этом контексте поле файлов автоматически проходит через Yup-валидацию при каждом изменении состояния формы или при сабмите.
Поскольку Yup ожидает массив, часто требуется преобразование
FileList:
const normalizeFiles = (fileList) => {
if (!fileList) return [];
return Array.from(fileList);
};
В React Hook Form это может быть реализовано через
setValue:
onCha nge={(e) => {
setValue("files", normalizeFiles(e.target.files));
}}
Без нормализации часть Yup-методов может работать некорректно или давать неожиданные результаты.
При множественной загрузке часто возникает необходимость исключить дубликаты:
files: Yup.array().test(
"uniqueFiles",
"Файлы должны быть уникальными",
(files) => {
if (!files) return true;
const names = files.map((f) => f.name);
return new Set(names).size === names.length;
}
)
Такой подход использует имя файла как идентификатор, что достаточно для клиентской валидации, хотя в серверных системах может применяться хеширование.
Иногда требования зависят от других полей формы. Например, обязательность загрузки зависит от типа сущности:
files: Yup.array().when("type", {
is: "withFiles",
then: (schema) => schema.min(1).required(),
otherwise: (schema) => schema.notRequired()
})
Это позволяет строить динамическую валидацию без внешних условий.
Yup поддерживает transform, который полезен для
приведения данных к стабильному виду:
files: Yup.array()
.transform((value) => value || [])
.of(Yup.mixed())
Это устраняет проблемы с undefined, которые часто
возникают при инициализации формы.
После успешной валидации массив файлов обычно преобразуется в
FormData:
const formData = new FormData();
files.forEach((file) => {
formData.append("files", file);
});
Важно, что порядок файлов в массиве сохраняется, и Yup не изменяет структуру данных, что обеспечивает предсказуемость на этапе отправки.
При сбросе формы требуется синхронное очищение как UI, так и состояния Yup:
reset({
files: []
});
Пустой массив является корректным состоянием для Yup-схем и не
вызывает ошибок, если не задан required.
На практике встречаются повторяющиеся проблемы:
FileList без преобразования в массивmin(1) при required()undefined при инициализации формыКорректная схема должна учитывать все эти аспекты и оставаться детерминированной независимо от UI-реализации.