这是个人回忆录。

环境

  • 反应:18.2.0
  • 下一个:12.2.3
  • react-dropzone: 14.2.2
  • @chakra-ui/反应:2.2.4

你想做的事

  • 将多个图像文件拖放到一个页面上
  • 预览所选图像
  • 预览图可以删除
  • 将所选图像上传到数据库
  • 库使用react-dropzone

完整图像

【React / Next】ドラッグ&ドロップで画像を複数選択してアップロードする

安装

  • 首先,安装react-dropzone
yarn add react-dropzone

示例代码

内容.tsx
import { Img } from '@chakra-ui/react';
import { useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { useImageNew } from 'usecase/useImageNew';
import styles from './contents.module.scss';

export const Contents = () => {
  // ブラウザ表示用の paths
  const [previewImagePaths, setPreviewImagePaths] = useState<string[]>();
  // upload用の files
  const [files, setFiles] = useState<File[]>();

  const { imageNewHandler } = useImageNew();

  //*********************
  /** 選択された画像を処理 */
  //*********************
  const onDrop = (acceptedFiles: File[]) => {
    // 引数で受け取れる値は、File型の配列なので upload用のstateへsetする
    setFiles(acceptedFiles);
    // ブラウザで画像を表示させるための、一時的なURLをメモリに生成する
    // @see https://developer.mozilla.org/ja/docs/Web/API/URL/createObjectURL
    const dataUrls = acceptedFiles.map((file) => URL.createObjectURL(file));
    // ブラウザ表示用のstateへsetする
    setPreviewImagePaths(dataUrls);
  };
  const { getRootProps, getInputProps } = useDropzone({ onDrop });

  //************************************************
  /** 削除ボタンをクリックすると、プレビュー画像を削除する */
  //************************************************
  const handleClickDelete = () => {
    setPreviewImagePaths([]);
  };

  //*********************************
  /** アップロードボタンクリック時の処理 */
  //*********************************
  const handleClickUpload = async () => {
    const formData = buildFormData(files);
    const response = await imageNewHandler(formData);
    // 省略
  };

  const buildFormData = (files?: File[]) => {
    if (!files) {
      return new FormData();
    }
    // DB へアップロードするために、FormData へ append する
    // @see https://developer.mozilla.org/ja/docs/Web/API/FormData/Using_FormData_Objects
    const formData = new FormData();
    files.forEach((file) => formData.append(file.name, file, file.name));
    return formData;
  };

  return (
    <>
      <div className={styles.contents}>
        <div {...getRootProps({ className: 'dropzone' })}>
          <input {...getInputProps()} accept="image/*" />
          <p>
            Drag and drop some image files here, or click to select image files
          </p>
        </div>
      </div>
      {previewImagePaths &&
        previewImagePaths.map((image, i) => (
          <div key={i}>
            <Img src={image} />
          </div>
        ))}
      <div className={styles.contents__button}>
        <button onClick={handleClickDelete}>Delete preview images</button>
      </div>
      <div className={styles.contents__button}>
        <button onClick={handleClickUpload}>Upload images</button>
      </div>
    </>
  );
};
内容.module.scss
.contents {
  margin: 0 auto;
  border: 2px dashed #ccc;
  background-color: #eee;
  color: #bbb;
  max-width: 600px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;

  &__button {
    margin-top: 20px;
  }
}
使用ImageNew.ts
import { ImagePostResponse } from 'entity/dropzone/imagePost';
import baseHttpClient from 'infrastructure/httpClient';

// NOTE: 簡易的なレイヤー
export const useImageNew = () => {
  const imageNewHandler = async (formData: FormData) => {
    const response = await baseHttpClient.post<ImagePostResponse>(
      `/api/image`,
      formData,
      {
       // content-type を multipart/form-data に指定
       // @see https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Type
       // @see https://developer.mozilla.org/ja/docs/Web/API/FormData/Using_FormData_Objects
        headers: {
          'content-type': 'multipart/form-data',
        },
      },
    );

    return response?.data;
  };

  return {
    imageNewHandler,
  };
};

模态模式

  • 模式打开模态然后拖放

完整图像

【React / Next】ドラッグ&ドロップで画像を複数選択してアップロードする

示例代码

内容.tsx
import { Img, useDisclosure } from '@chakra-ui/react';
import { useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { useImageNew } from 'usecase/useImageNew';
import { ImageModal } from '../modal/modal';
import styles from './contents.module.scss';

export const Contents = () => {
  const { isOpen, onOpen, onClose } = useDisclosure();
  // ブラウザ表示用の paths
  const [previewImagePaths, setPreviewImagePaths] = useState<string[]>();
  // upload用の files
  const [files, setFiles] = useState<File[]>();

  const { imageNewHandler } = useImageNew();

  //*********************
  /** 選択された画像を処理 */
  //*********************
  const selectImages = (acceptedFiles: File[]) => {
    setFiles(acceptedFiles);
    const dataUrls = acceptedFiles.map((file) => URL.createObjectURL(file));
    setPreviewImagePaths(dataUrls);
  };

  const handleClickDelete = () => {
    setPreviewImagePaths([]);
  };

  const handleClickUpload = async () => {
    const formData = buildFormData(files);
    const response = await imageNewHandler(formData);
  };

  const buildFormData = (files?: File[]) => {
    if (!files) {
      return new FormData();
    }
    // DB へアップロードするために、FormData へ append する
    const formData = new FormData();
    files.forEach((file) => formData.append(file.name, file, file.name));
    return formData;
  };

  const handleClickModal = () => {
    onOpen();
  };

  return (
    <div className={styles.contents}>
      {previewImagePaths &&
        previewImagePaths.map((image, i) => (
          <div key={i}>
            <Img src={image} />
          </div>
        ))}
      <ImageModal
        isOpen={isOpen}
        onClose={onClose}
        selectImages={selectImages}
      />
      <div className={styles.contents__button}>
        <button onClick={handleClickDelete}>Delete preview images</button>
      </div>
      <div className={styles.contents__button}>
        <button onClick={handleClickUpload}>Upload images</button>
      </div>
      <div className={styles.contents__button}>
        <button onClick={handleClickModal}>Open modal</button>
      </div>
    </div>
  );
};
模态的.tsx
import {
  Box,
  Modal,
  ModalBody,
  ModalContent,
  ModalFooter,
  ModalHeader,
  ModalOverlay,
} from '@chakra-ui/react';
import { useDropzone } from 'react-dropzone';
import styles from './modal.module.scss';

type Props = {
  isOpen: boolean;
  onClose: () => void;
  selectImages: (acceptedFiles: File[]) => void;
};

export const ImageModal = ({ isOpen, onClose, selectImages }: Props) => {
  //************************************
  /** 選択された画像を処理するイベントを通知 */
  //************************************
  const onDrop = (acceptedFiles: File[]) => {
    selectImages(acceptedFiles);
  };
  const { getRootProps, getInputProps } = useDropzone({ onDrop });

  const handleClickOk = () => {
    onClose();
  };

  return (
    <Modal isOpen={isOpen} onClose={onClose} isCentered size={'2xl'}>
      <ModalOverlay />
      <ModalContent>
        <ModalHeader className={styles.modal__header}>画像の選択</ModalHeader>
        <ModalBody className={styles.modal__body}>
          <div className={styles.modal__contents}>
            <div {...getRootProps({ className: 'dropzone' })}>
              <input {...getInputProps()} accept="image/*" />
              <p>
                Drag and drop some image files here, or click to select image
                files
              </p>
            </div>
          </div>
        </ModalBody>
        <ModalFooter>
          <Box className={styles.modal__footer}>
            <button onClick={handleClickOk}>OK</button>
          </Box>
        </ModalFooter>
      </ModalContent>
    </Modal>
  );
};

参考

关于 react-dropzone

关于 multipart/form-data

关于表单数据

关于文件阅读器

关于文件/Blob

关于 Base64

关于每个功能


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308622431.html

相关文章: