Работа с файлами и медиа-контентом

Hyperapp — это минималистичный JavaScript-фреймворк для построения реактивных пользовательских интерфейсов. Его ядро сосредоточено на управлении состоянием и виртуальном DOM, что делает работу с файлами и медиа-контентом особенной задачей, требующей интеграции стандартных веб-API с архитектурой Hyperapp.


Управление состоянием для файлов

Файлы, загружаемые пользователем через <input type="file">, необходимо хранить в состоянии приложения. Hyperapp использует объект состояния (state) для реактивного обновления интерфейса:

import { h, app } from "https://unpkg.com/hyperapp?module";

const state = {
  files: []
};

const actions = {
  setFiles: files => state => ({ files }),
};

const view = (state, actions) =>
  h("div", {}, [
    h("input", {
      type: "file",
      multiple: true,
      onchange: e => actions.setFiles(Array.from(e.target.files))
    }),
    h("ul", {}, state.files.map(file => h("li", {}, file.name)))
  ]);

app({ init: state, view, actions });

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

  • Состояние хранит массив объектов File.
  • onchange события <input> обрабатывает файлы и обновляет состояние через action.
  • Преобразование FileList в массив через Array.from позволяет удобно использовать .map.

Чтение содержимого файлов

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

const actions = {
  readFile: file => state => {
    const reader = new FileReader();
    reader.onl oad = () => {
      state.filesContent = [...(state.filesContent || []), reader.result];
    };
    reader.readAsText(file);
    return state;
  }
};

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

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

Работа с изображениями и медиа-контентом

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

const actions = {
  previewImage: file => state => {
    const reader = new FileReader();
    reader.onl oad = () => {
      state.preview = reader.result;
    };
    reader.readAsDataURL(file);
    return state;
  }
};

const view = (state, actions) =>
  h("div", {}, [
    h("input", {
      type: "file",
      accept: "image/*",
      onchange: e => actions.previewImage(e.target.files[0])
    }),
    state.preview && h("img", { src: state.preview, width: 200 })
  ]);

Выделенные аспекты:

  • readAsDataURL преобразует файл в строку base64, удобную для <img> или <video>.
  • Состояние preview автоматически обновляет интерфейс при загрузке.
  • Можно расширить для поддержки нескольких изображений или видео.

Загрузка файлов на сервер

Для отправки файлов на сервер используется FormData и стандартный fetch:

const actions = {
  uploadFile: file => async state => {
    const formData = new FormData();
    formData.append("file", file);

    const response = await fetch("/upload", {
      method: "POST",
      body: formData
    });

    if (response.ok) {
      state.uploadStatus = "Файл загружен";
    } else {
      state.uploadStatus = "Ошибка загрузки";
    }
    return state;
  }
};

Особенности интеграции:

  • Асинхронные действия (async) возвращают обновлённое состояние.
  • Статус загрузки можно хранить в state для отображения прогресса.
  • Можно комбинировать с прогресс-барами через XMLHttpRequest и onprogress.

Стриминг и предварительный просмотр больших файлов

Для больших видео или аудио-файлов важно не загружать весь контент в память. Использование URL.createObjectURL позволяет создавать временные ссылки:

const actions = {
  createPreviewURL: file => state => {
    state.mediaURL = URL.createObjectURL(file);
    return state;
  }
};

const view = (state, actions) =>
  h("div", {}, [
    h("input", { type: "file", accept: "video/*", onchange: e => actions.createPreviewURL(e.target.files[0]) }),
    state.mediaURL && h("video", { src: state.mediaURL, controls: true, width: 400 })
  ]);

Преимущества:

  • Минимизируется использование памяти.
  • Видео или аудио начинают воспроизводиться до полной загрузки.
  • Временный URL освобождается через URL.revokeObjectURL, когда он больше не нужен.

Интеграция с drag-and-drop

Hyperapp легко комбинивается с событиями dragover и drop для загрузки файлов перетаскиванием:

const actions = {
  dropFiles: files => state => ({ files })
};

const view = (state, actions) =>
  h("div", {
    ondragover: e => e.preventDefault(),
    ondrop: e => {
      e.preventDefault();
      actions.dropFiles(Array.from(e.dataTransfer.files));
    },
    style: { border: "2px dashed #ccc", padding: "20px", width: "300px" }
  }, "Перетащите файлы сюда");

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

  • e.preventDefault() обязателен для корректного приема событий drop.
  • Можно комбинировать с предыдущими методами для чтения или предварительного просмотра.

Оптимизация и рекомендации

  • Для больших файлов использовать потоковую обработку и createObjectURL.
  • Хранить только ссылки на файлы или их идентификаторы в состоянии, а не сами бинарные данные.
  • Асинхронные действия стоит оборачивать через actions, чтобы сохранялась реактивность Hyperapp.
  • Обрабатывать ошибки чтения файлов и сетевых запросов для стабильности интерфейса.

Работа с файлами и медиа-контентом в Hyperapp строится на сочетании реактивного состояния и стандартных веб-API (FileReader, FormData, URL.createObjectURL). Минималистичная архитектура Hyperapp позволяет интегрировать эти возможности без сложных обёрток, сохраняя скорость и предсказуемость приложения.