Обработка файловых загрузок

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


Выбор файлов через input

Для загрузки файлов используется стандартный HTML-элемент <input type="file">. В Mithril его можно обернуть в компонент с управлением состоянием:

const FileUploader = {
    file: null,

    view: function() {
        return m("div",
            m("input[type=file]", {
                onchange: (e) => FileUploader.file = e.target.files[0]
            }),
            m("button", {onclick: FileUploader.upload}, "Загрузить файл")
        );
    },

    upload: function() {
        if (!FileUploader.file) return;

        const formData = new FormData();
        formData.append("file", FileUploader.file);

        m.request({
            method: "POST",
            url: "/upload",
            body: formData,
            serialize: (data) => data // FormData не нужно сериализовать
        }).then(response => {
            console.log("Файл успешно загружен", response);
        }).catch(err => {
            console.error("Ошибка загрузки", err);
        });
    }
};

Ключевые моменты:

  • Использование FormData для отправки бинарных данных.
  • Установка serialize: (data) => data в m.request, чтобы Mithril не пытался сериализовать FormData.
  • Хранение выбранного файла в состоянии компонента.

Множественный выбор файлов

Для загрузки нескольких файлов элемент input поддерживает атрибут multiple. В этом случае e.target.files содержит массивоподобный объект FileList.

const MultiFileUploader = {
    files: [],

    view: function() {
        return m("div",
            m("input[type=file][multiple]", {
                onchange: (e) => MultiFileUploader.files = Array.from(e.target.files)
            }),
            m("button", {onclick: MultiFileUploader.uploadAll}, "Загрузить все файлы")
        );
    },

    uploadAll: function() {
        if (MultiFileUploader.files.length === 0) return;

        const formData = new FormData();
        MultiFileUploader.files.forEach((file, index) => {
            formData.append(`file${index}`, file);
        });

        m.request({
            method: "POST",
            url: "/upload-multiple",
            body: formData,
            serialize: data => data
        }).then(response => {
            console.log("Файлы успешно загружены", response);
        }).catch(err => {
            console.error("Ошибка загрузки файлов", err);
        });
    }
};

Особенности:

  • Преобразование FileList в массив для удобного перебора.
  • Индексация файлов при добавлении в FormData, чтобы сервер мог корректно их обработать.

Прогресс загрузки

Для отображения прогресса загрузки необходимо использовать XMLHttpRequest, так как m.request не предоставляет доступ к событиям прогресса.

const FileUploaderWithProgress = {
    file: null,
    progress: 0,

    view: function() {
        return m("div",
            m("input[type=file]", {
                onchange: (e) => FileUploaderWithProgress.file = e.target.files[0]
            }),
            m("button", {onclick: FileUploaderWithProgress.upload}, "Загрузить"),
            m("progress", {value: FileUploaderWithProgress.progress, max: 100})
        );
    },

    upload: function() {
        if (!FileUploaderWithProgress.file) return;

        const formData = new FormData();
        formData.append("file", FileUploaderWithProgress.file);

        const xhr = new XMLHttpRequest();
        xhr.open("POST", "/upload-progress", true);

        xhr.upload.onprogr ess = function(event) {
            if (event.lengthComputable) {
                FileUploaderWithProgress.progress = Math.round((event.loaded / event.total) * 100);
                m.redraw();
            }
        };

        xhr.onl oad = function() {
            if (xhr.status === 200) {
                console.log("Файл загружен");
                FileUploaderWithProgress.progress = 0;
            } else {
                console.error("Ошибка загрузки");
            }
        };

        xhr.send(formData);
    }
};

Важные моменты:

  • xhr.upload.onprogress позволяет отслеживать прогресс передачи данных.
  • m.redraw() принудительно обновляет интерфейс, чтобы прогрессбар отражал текущий процент загрузки.

Ограничения и проверка файлов

Перед отправкой желательно проверять размер и тип файла:

const MAX_SIZE = 5 * 1024 * 1024; // 5 МБ
const ALLOWED_TYPES = ["image/png", "image/jpeg"];

const FileValidator = {
    validate(file) {
        if (file.size > MAX_SIZE) {
            console.error("Файл слишком большой");
            return false;
        }
        if (!ALLOWED_TYPES.includes(file.type)) {
            console.error("Недопустимый тип файла");
            return false;
        }
        return true;
    }
};

Использование:

if (!FileValidator.validate(FileUploader.file)) return;

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


Drag & Drop

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

const DragDropUploader = {
    files: [],

    view() {
        return m("div.dropzone", {
            ondragover: (e) => e.preventDefault(),
            ondrop: (e) => {
                e.preventDefault();
                DragDropUploader.files = Array.from(e.dataTransfer.files);
            }
        }, "Перетащите файлы сюда");
    }
};

Особенности:

  • ondragover обязательно отменяет стандартное поведение, иначе событие drop не срабатывает.
  • e.dataTransfer.files содержит файлы, которые были перетащены.

Отправка больших файлов

При работе с большими файлами стоит использовать загрузку частями (chunked upload) или WebSockets. Mithril позволяет интегрировать эти методы через XMLHttpRequest или сторонние библиотеки, сохраняя реактивность интерфейса.


Совместимость и безопасность

  • Поддержка FormData есть во всех современных браузерах.
  • Обязательно проверять тип и размер файлов на сервере.
  • Для загрузки больших файлов стоит учитывать ограничения сервера и настраивать таймауты и ограничения памяти.

Обработка файлов в Mithril строится на стандартных веб-технологиях, а фреймворк обеспечивает удобное связывание состояния компонента с пользовательским интерфейсом, управление событиями и обновление DOM. Использование FormData, XMLHttpRequest и событий прогресса позволяет создать гибкую систему загрузки с поддержкой как одиночных, так и множественных файлов, с контролем прогресса и валидацией.