[React] React 이미지 업로드

기록저장소·2023년 12월 29일

회사에서 현재 멀티 업로드로 구현되어 있지 않고 단일 업로드 기능을 제공하고 있어 멀티 업로드 기능을 프로토타이핑해봤다.

기능은 미리보기, drag&drop으로 이미지 위치변경이 있다.

Code

Script

const [uploadList, setUploadList] = useState<FileItem[]>([]);
const inputRef = useRef<HTMLInputElement>(null);
const uploadImg = async (e: ChangeEvent<HTMLInputElement>) => {
  e.preventDefault();

  const tempUploadList: FileItem[] = [];
  const files = e.target.files;

  if (files) {
    for (let i = 0; i < files.length; i++) {
      const preview = URL.createObjectURL(files[i]);
      const type = files[i].type.split('/')[0];

      tempUploadList.push({
        fileObject: files[i],
        preview,
        type
      });
    }
  }

  setUploadList(prevList => [...prevList, ...tempUploadList]);
};

const deleteImage = (index: number) => {
  const tempUploadList = [...uploadList];

  // 메모리 누수 방지를 URL.createObjectURL()을 통해 생성한 객체 URL을 해제
  URL.revokeObjectURL(tempUploadList[index].preview);

  tempUploadList.splice(index, 1);

  setUploadList(tempUploadList);
};

const onDragStart = (e: React.DragEvent<HTMLDivElement>, id: number) => {
  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('imgIndex', String(id));
};

const onDragDrop = (e: React.DragEvent, index: number) => {
  e.preventDefault();

  const sourceIndex = Number(e.dataTransfer.getData('imgIndex'));
  
  if (sourceIndex === index) return;
  
  const updateImages = [...uploadList];
  const [movedImage] = updateImages.splice(sourceIndex, 1);

  updateImages.splice(index, 0, movedImage);
  
  setUploadList(updateImages);
};

const onDragOver = (e: React.DragEvent) => {
  e.preventDefault();
};

useEffect(() => {
  return () => {
    uploadList.forEach(item => {
      URL.revokeObjectURL(item.preview);
    });
  };
}, [uploadList]);

JSX

<S.Container>
  <S.Input
    type="file"
    multiple={true}
    accept="video/*, image/*"
	onChange={uploadImg}
	onClick={e => ((e.target as HTMLInputElement).value = '')}
    ref={inputRef}
  />
  <h4>파일 업로드 영역</h4>
  <S.FileContainer>
    {uploadList.map((item, index) => (
      <div className="filelist" key={item.preview}>
        {item.type === 'image' ? (
          <img
            src={item.preview}
            draggable
            onDragStart={e => onDragStart(e, index)}
            onDragOver={onDragOver}
            onDrop={e => onDragDrop(e, index)}
            alt={`Preview ${index}`} />
        ) : (
          <video src={item.preview} autoPlay={false} controls={true} />
        )}
        <div
          className="del-btn"
          onClick={() => {
            deleteImage(index);
          }}
        >X</div>
      </div>
    ))}
  </S.FileContainer>
  <button onClick={() => inputRef.current?.click()}>Upload</button>
</S.Container>

Demo

profile
기록을 남기는 공간.

0개의 댓글