회사에서 현재 멀티 업로드로 구현되어 있지 않고 단일 업로드 기능을 제공하고 있어 멀티 업로드 기능을 프로토타이핑해봤다.
기능은 미리보기, drag&drop으로 이미지 위치변경이 있다.
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]);
<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>
