DevGang
Авторизоваться

Загрузка изображений в React

Одним из требований к моему выпускному проекту в Flatiron School было включение в него того, что ранее не изучалось в рамках учебного курса. Из-за своей любви к красивому дизайну я выбрал загрузку изображений для выполнения этого требования. Продумав цикл "запрос - ответ", мне нужно было научиться выбирать изображение через файловый браузер, загружать его через запрос POST или PATCH, хранить в базе данных и возвращать изображение с помощью запроса GET.

Настройка

Прежде чем мы продолжим, скажу, что моё приложение построено с использованием:

  • React v18.2.0
  • Ruby v2.7.4
  • Rails v7.0.5

Выбор файла

Хотя в процессе обучения я начал с бэкенда, я решил, что имеет смысл представить это руководство в порядке цикла "запрос - ответ".

Помимо возможности доступа к файловому браузеру для выбора изображения для загрузки, мне также хотелось иметь возможность перемещать это изображение. Изучая несколько вариантов, я наткнулся на react-dropzone, "простой React-хук для создания HTML5-совместимой зоны перемещения файлов". Установка была простой, а документация хорошо написана и содержит множество примеров различных приложений.

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

Я создал компонент DropZone, который будет использоваться в различных формах моего приложения. Этот компонент получает реквизит setState, используемый в хуке useCallback. Хотя DropZone может работать с файлами любого типа, я настроил свой компонент так, чтобы он принимал только изображения.

import { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';

function DropZone({ setState }) {
  const onDrop = useCallback(acceptedFiles => {
    setState(acceptedFiles[0])
  }, []);

  const {getRootProps, getInputProps, isDragActive} = useDropzone({
    onDrop, 
    accept: {'image/*': []}
  });

  return (
    <div {...getRootProps()} className={isDragActive ? 'active' : ""}>
      <input {...getInputProps()} />
      {
        isDragActive
          ? <p>Drop the file here ...</p> 
          : <p>Drag 'n' drop an image here—or click to select file</p>
      }
    </div>
  );
}

export default DropZone;

Это простое использование react-dropzone. Он имеет гораздо больше возможностей, включая возможность предварительного просмотра изображения после его загрузки в состояние.

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

Прикрепление и извлечение

Этот шаг потребовал небольшого исследования. При обычном запросе POST или PATCH я использовал JSON.stringify() в теле запроса. Однако когда потребовалось прикрепить файл, это решение оказалось неудачным.

Вместо этого я использовал конструктор FormData и его метод append() для присоединения файла изображения.

function handleImgSubmit(e, setErrors, form, img, setUser) {
    e.preventDefault();
    setErrors();

    const profile = new FormData();
    profile.append('first_name', form.firstName); 
    profile.append('last_name', form.lastName);
    profile.append('phone', form.phone); 
    profile.append('city', form.city);
    profile.append('state', form.state);
    profile.append('bio', form.bio);
    profile.append('venue_id', form.venueId);
    profile.append('video_url', form.videoUrl);

    if(img) {
        profile.append('avatar', img);
    }

// Important! Do not add headers to fetch requests when using FormData()
    fetch("/profiles", {
        method: "POST",
        body: profile
    })
    .then(r => {
        if(r.ok) {
            r.json().then((data) => { setUser(data) });
        } else {
            r.json().then((err) => setErrors(err.errors));
        }
    });
}

Этот метод безупречно работает при отправке файлов на бэкенд. Обратите внимание, запрос на выборку не содержит headers или JSON.stringify(). Конструктор FormData позаботится об этих проблемах, и отправка fetch-запроса с headers приведет к ошибке сервера. FormData можно использовать в любом запросе, независимо от того, прикреплен файл или нет. Это позволяет сэкономить немного кода при рефакторинге, как в данном примере.

const data = new FormData();
Object.keys(form).map(key => data.append(key, form[key]));

Когда фронтенд завершен, следующим шагом будет создание места для размещения файла.

Управление базой данных

Существует множество различных способов управления хранением файлов на бэкенде. Поскольку мой бэкенд представляет собой Rails API, я решил, что имеет смысл использовать Active Storage. Как указано в документации, для преобразования обработки изображений требуется стороннее программное обеспечение и гем image_processing. Active Storage позволяет легко конфигурировать онлайн-сервисы хранения данных, предлагаемые через Amazon, Google или Microsoft Azure.

Для того чтобы связать изображение с записью, используйте макрос has_one_attached.

class Profile < ApplicationRecord
  belongs_to :user
  belongs_to :venue
  has_one_attached :avatar
end

Rails 6.0+ также позволяет перейти к включению attachment в качестве типа атрибута. rails generate model Profile avatar:attachment.

В контроллере вызовите avatar.attach, чтобы прикрепить файл изображения к профилю.

class ProfilesController < ApplicationController
  def create
    profile = @current_user.build_profile(profile_params) if @current_user.profile.nil?
    profile.avatar.attach(profile_params[:avatar]) unless profile_params[:avatar].nil?
    venue = Venue.find(profile_params[:venue_id])
    venue.profiles << profile
    profile.save!
    render json: @current_user, status: :created
  end

  private
  def profile_params
    params.permit(:avatar, :first_name, :last_name, :bio, :phone, :city, :state, :venue_id, :video_url)
  end
end

Теперь файл изображения прикреплен и связан с нужной записью. Как отправить его обратно на фронтенд?

Ответ

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

В состав Rails входит помощник для доступа к URL-адресу прикрепленного файла изображения. Сначала добавьте помощник в класс с помощью include. Затем создайте частный метод с использованием помощника url_for для доступа к URL-адресу файла изображения.

class ProfileSerializer < ActiveModel::Serializer
  include Rails.application.routes.url_helpers

  attributes :id, :avatar, :first_name, :last_name, :phone, :city, :state, :bio, :video_url
  has_one :user
  has_one :venue

  private
  def avatar
    url_for(object.avatar) if object.avatar.attached?
  end
end

Теперь URL-адрес файла изображения будет сериализован при формировании контроллером JSON.

Заключение

Вот и все! Работа с файлами изображений относительно проста. Использование хуков React, таких как react-dropzone, позволяет очень просто создать элегантный селектор файлов. Конструктор FormData выполняет фронтенд-присоединение, а Active Storage управляет ассоциациями и доступом на стороне сервера. Возврат изображения теперь сводится к возврату URL-адреса изображения. Надеюсь, этот обзор поможет вам. Счастливого кодинга!

Источник:

#JavaScript #React
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу