Использование API FileReader для предварительного просмотра изображений в React

Автор Джозеф Мава✏️

Введение

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

В среде браузера клиенты могут инициировать загрузку изображений, просматривая файлы с помощью элемента input или API перетаскивания. Затем вы можете использовать API URL или API FileReader для чтения файлов изображений и их предварительного просмотра.

Хотя предварительный просмотр изображений с помощью API URL прост, использование API FileReader может оказаться сложным. Поэтому в этой статье вы узнаете, как просматривать изображения в React-приложении с помощью API FileReader. Мы рассмотрим как одиночный, так и пакетный предварительный просмотр изображений.

Содержание

  • Как просматривать файлы изображений в React
  • Введение в API FileReader
  • Как просмотреть одно изображение перед загрузкой в React с помощью API FileReader
  • Как просматривать несколько изображений перед загрузкой в React с помощью API FileReader
  • Заключение

Как просматривать файлы изображений в React

Если вы хотите добавить в свое веб-приложение функцию загрузки файлов, вам пригодится элемент input типа file. Он позволяет пользователям выбирать один или несколько файлов из хранилища на компьютере или мобильном устройстве:

<input type="file" accept="image/*" multiple />
Вход в полноэкранный режим Выйти из полноэкранного режима

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

Элемент input имеет атрибут accept для ограничения типа файла. Его значением является строка, состоящая из спецификаторов типов файлов, разделенных запятыми. Значение атрибута accept в элементе input выше — image/*. Он позволяет просматривать и загружать изображения любого формата.

Чтобы загружать файлы изображений определенного формата, можно ограничить значение атрибута accept. Например, установив его значение image/png или .png, можно принимать только изображения PNG.

При установке булева атрибута multiple в значение true пользователь может выбрать несколько файлов изображений. С другой стороны, пользователь может просматривать только один файл изображения, если его значение false. Стоит отметить, что значение булева атрибута true, если атрибут присутствует на элементе, и false, если опущен.

Браузер выдает событие change после того, как пользователь завершает выбор файла. Поэтому вы должны прослушать событие change на элементе input. В React это можно сделать следующим образом:

<form>
  <p>
    <label htmlFor="file">Upload images</label>
    <input
      type="file"
      id="file"
      onChange={changeHandler}
      accept="image/*"
      multiple
    />
  </p>
</form>
Вход в полноэкранный режим Выйти из полноэкранного режима

В обработчике события change вы можете получить доступ к объекту FileList. Это итерабельный список, элементами которого являются объекты File. Объекты File содержат метаданные, доступные только для чтения, такие как имя, тип и размер файла:

const changeHandler = (e) => {
  const { files } = e.target
  for (let i = 0; i < files.length; i++) {
    const file = files[i]; // OR const file = files.item(i);
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Введение в API FileReader

API FileReader предоставляет интерфейс для асинхронного чтения содержимого файла из веб-приложения.

Как было показано в предыдущем разделе, вы можете использовать элемент input типа file для просмотра файлов с компьютера или мобильного устройства пользователя. Выбор файлов изображений таким способом возвращает объект FileList, элементами которого являются объекты File.

Затем API FileReader использует объект File для асинхронного чтения выбранного пользователем файла. Стоит отметить, что вы не можете использовать API FileReader для чтения содержимого файла из файловой системы пользователя, используя имя файла.

API FileReader имеет несколько асинхронных методов экземпляра для выполнения операций чтения. К этим методам относятся:

  • readAsArrayBuffer
  • readAsBinaryString
  • readAsDataURL
  • readAsText

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

Он испускает событие change после завершения операции read:

const fileReader = new FileReader();

fileReader.onchange = (e) => {
   const { result } = e.target;
}

fileReader.readAsDataURL(fileObject);
Вход в полноэкранный режим Выход из полноэкранного режима

Подробное описание других методов экземпляра FileReader вы можете прочитать в документации.

Как просмотреть одно изображение перед загрузкой в React

В этом разделе мы рассмотрим, как предварительно просмотреть одно изображение перед загрузкой в React с помощью API FileReader. Предполагается, что у вас уже настроен проект React.

Код ниже показывает, как прочитать и просмотреть одно изображение в React с помощью API FileReader. Мы используем элемент input типа file для просмотра файлов изображений. Поскольку мы хотим просматривать одно изображение, я опустил атрибут multiple boolean на элементе input:

import { useEffect, useState } from 'react';

const imageMimeType = /image/(png|jpg|jpeg)/i;

function App() {
  const [file, setFile] = useState(null);
  const [fileDataURL, setFileDataURL] = useState(null);

  const changeHandler = (e) => {
    const file = e.target.files[0];
    if (!file.type.match(imageMimeType)) {
      alert("Image mime type is not valid");
      return;
    }
    setFile(file);
  }
  useEffect(() => {
    let fileReader, isCancel = false;
    if (file) {
      fileReader = new FileReader();
      fileReader.onload = (e) => {
        const { result } = e.target;
        if (result && !isCancel) {
          setFileDataURL(result)
        }
      }
      fileReader.readAsDataURL(file);
    }
    return () => {
      isCancel = true;
      if (fileReader && fileReader.readyState === 1) {
        fileReader.abort();
      }
    }

  }, [file]);

  return (
    <>
      <form>
        <p>
          <label htmlFor='image'> Browse images  </label>
          <input
            type="file"
            id='image'
            accept='.png, .jpg, .jpeg'
            onChange={changeHandler}
          />
        </p>
        <p>
          <input type="submit" label="Upload" />
        </p>
      </form>
      {fileDataURL ?
        <p className="img-preview-wrapper">
          {
            <img src={fileDataURL} alt="preview" />
          }
        </p> : null}
    </>
  );
}
export default App;
Вход в полноэкранный режим Выйти из полноэкранного режима

Как показано в примере выше, вы можете прослушивать событие change на элементе input. Обработчик события change вызывается после того, как клиент завершает выбор файла. Вы можете получить доступ к объекту File, представляющему выбранный файл, и обновить состояние в обработчике события.

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

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

Поскольку чтение выбранного файла является побочным эффектом, мы используем хук useEffect. Как было показано в предыдущем разделе, вы начинаете с создания экземпляра FileReader. Метод readAsDataURL API FileReader асинхронно считывает файл и выдает событие load после завершения процесса чтения.

Возможно, что компонент размонтируется или перезагрузится до завершения процесса чтения. Если процесс чтения не завершен, необходимо прервать его перед размонтированием. Чтобы предотвратить утечку памяти, React запрещает обновление состояния после размонтирования компонента. Поэтому перед обновлением состояния в обработчике события load нам нужно проверить, смонтирован ли компонент.

Мы получаем доступ к данным файла в виде base64-кодированной строки и обновляем состояние после завершения процесса чтения. После этого можно выводить предварительный просмотр изображения. Для простоты я не добавил никаких стилей к элементу form в приведенном примере.

Как сделать предварительный просмотр нескольких изображений перед загрузкой в React

В этом разделе мы рассмотрим, как просматривать несколько изображений перед загрузкой в React с помощью API FileReader. Как и в предыдущем разделе, предполагается, что у вас уже есть созданный проект React.

Чтение и предварительный просмотр нескольких изображений аналогичен предварительному просмотру одного изображения. Мы немного изменим код в предыдущем разделе. Чтобы просмотреть и выбрать несколько файлов изображений, нужно установить значение булевого атрибута multiple в true на элементе input.

Одно заметное отличие заключается в том, что мы перебираем объект FileList в хуке useEffect и читаем содержимое всех выбранных файлов перед обновлением состояния. Мы храним URL данных каждого файла изображения в массиве и обновляем состояние после чтения последнего файла.

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

import { useEffect, useState } from "react";

const imageTypeRegex = /image/(png|jpg|jpeg)/gm;

function App() {
  const [imageFiles, setImageFiles] = useState([]);
  const [images, setImages] = useState([]);

  const changeHandler = (e) => {
    const { files } = e.target;
    const validImageFiles = [];
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      if (file.type.match(imageTypeRegex)) {
        validImageFiles.push(file);
      }
    }
    if (validImageFiles.length) {
      setImageFiles(validImageFiles);
      return;
    }
    alert("Selected images are not of valid type!");
  };

  useEffect(() => {
    const images = [], fileReaders = [];
    let isCancel = false;
    if (imageFiles.length) {
      imageFiles.forEach((file) => {
        const fileReader = new FileReader();
        fileReaders.push(fileReader);
        fileReader.onload = (e) => {
          const { result } = e.target;
          if (result) {
            images.push(result)
          }
          if (images.length === imageFiles.length && !isCancel) {
            setImages(images);
          }
        }
        fileReader.readAsDataURL(file);
      })
    };
    return () => {
      isCancel = true;
      fileReaders.forEach(fileReader => {
        if (fileReader.readyState === 1) {
          fileReader.abort()
        }
      })
    }
  }, [imageFiles]);
  return (
    <div className="App">
      <form>
        <p>
          <label htmlFor="file">Upload images</label>
          <input
            type="file"
            id="file"
            onChange={changeHandler}
            accept="image/png, image/jpg, image/jpeg"
            multiple
          />
        </p>
      </form>
      {
        images.length > 0 ?
          <div>
            {
              images.map((image, idx) => {
                return <p key={idx}> <img src={image} alt="" /> </p>
              })
            }
          </div> : null
      }
    </div>
  );
}

export default App;
Вход в полноэкранный режим Выход из полноэкранного режима

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

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

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

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

useEffect(() => {
  const fileReaders = [];
  let isCancel = false;
  if (imageFiles.length) {
    const promises = imageFiles.map(file => {
      return new Promise((resolve, reject) => {
        const fileReader = new FileReader();
        fileReaders.push(fileReader);
        fileReader.onload = (e) => {
          const { result } = e.target;
          if (result) {
            resolve(result);
          }
        }
        fileReader.onabort = () => {
          reject(new Error("File reading aborted"));
        }
        fileReader.onerror = () => {
          reject(new Error("Failed to read file"));
        }
        fileReader.readAsDataURL(file);
      })
    });
    Promise
      .all(promises)
      .then(images => {
        if (!isCancel) {
          setImages(images);
        }
      })
      .catch(reason => {
        console.log(reason);
      });
  };
  return () => {
    isCancel = true;
    fileReaders.forEach(fileReader => {
      if (fileReader.readyState === 1) {
        fileReader.abort()
      }
    })
  }
}, [imageFiles]);
Вход в полноэкранный режим Выход из полноэкранного режима

Заключение

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

Вы можете инициировать загрузку файлов с устройства клиента с помощью элемента input типа file или с помощью интерфейса перетаскивания. После выбора изображений их можно предварительно просмотреть с помощью API URL или API FileReader. Хотя использование API URL может быть простым, API FileReader не является таковым.

Как подчеркивалось в статье, вы можете просматривать изображения по одному или пакетно. Надеюсь, эта статья дала вам представление о предварительном просмотре изображений в React с помощью API FileReader. Дайте мне знать, что вы думаете в разделе комментариев ниже.


Полная видимость производственных приложений React

Отладка приложений React может быть сложной задачей, особенно когда пользователи сталкиваются с проблемами, которые трудно воспроизвести. Если вы заинтересованы в мониторинге и отслеживании состояния Redux, автоматическом выявлении ошибок JavaScript, отслеживании медленных сетевых запросов и времени загрузки компонентов, попробуйте LogRocket.

LogRocket — это как видеорегистратор для веб- и мобильных приложений, записывающий буквально все, что происходит в вашем React-приложении. Вместо того чтобы гадать, почему возникают проблемы, вы можете собрать данные и отчитаться о том, в каком состоянии находилось ваше приложение в момент возникновения проблемы. LogRocket также отслеживает производительность вашего приложения, предоставляя такие показатели, как загрузка процессора клиента, использование памяти клиента и многое другое.

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

Модернизируйте отладку приложений React — начните мониторинг бесплатно.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *