리액트에서 어떻게 이미지를 여러장 업로드 시키고 미리보기를 어떻게 구현하는지 설명하려고 한다.
우선 input[type="file" multiple]을 통해 이미지를 업로드하면 다음과 같은 결과를 얻을 수 있다.
다중 업로드는 multiple 속성을 입력해주기만 하면 그리 어려운 문제는 아니다.
하지만 미리보기 기능은 위의 결과값에서 없는 상대경로를 알아내야하기 때문에 조금 까다롭다.
그렇다면 상대경로는 어떻게 알아낼 수 있을까 ?
정답은 바로 URL.createObjectUrl() 메소드를 사용하는 것이다.
이미지를 선택 후 받아온 file객체를 URL.createObjectUrl() 메소드의 매개변수로 넣어주게 되면 아래와 같은 상대경로를 반환 받을 수 있다.
✅ 이제 다중 업로드와 미리보기 기능을 구현하기 위해 알아야할 이론은 다 배웠다. ✅
이제 코드로 구현해보자.
↓로직은 아래와 같다.↓
⌲ input[type="file"]의 디자인을 바꾸기 위해서 label 태그를 사용한다.
⌲ label 태그에 onChange 이벤트를 등록한다.
⌲ onChange의 event객체를 이용해서 이미지 데이터들을 받아온다.
⌲ 받아온 이미지들을 순회하면서 URL.createObjectUrl()의 매개변수로 이미지를 넣고 상대경로를 반환 받는다.
⌲ 반환 받은 상대경로를 배열로 관리하고 useState를 통해 데이터를 관리한다.
⌲ 상대경로를 가지고 있는 배열을 순회하면서 img태그의 src 속성에 값을 할당하고 렌더링 시킨다.
🔐 최대 업로드 할 수 있는 이미지를 10개로 제한한다.
const handleAddImages = (event) => {
const imageLists = event.target.files;
let imageUrlLists = [...showImages];
for (let i = 0; i < imageLists.length; i++) {
const currentImageUrl = URL.createObjectURL(imageLists[i]);
imageUrlLists.push(currentImageUrl);
}
if (imageUrlLists.length > 10) {
imageUrlLists = imageUrlLists.slice(0, 10);
}
setShowImages(imageUrlLists);
};
❗️ FileList는 배열이 아닌 객체형태로 반환된다. 그렇다면 length 속성을 이용해서 순회할 수 있을까 ?
FileList는 기본적으로 length라는 속성을 갖는다. length 속성을 이용해서 순회를 했다고 배열이라고 착각하면 안된다.
const Images = () => {
const [showImages, setShowImages] = useState([]);
// 이미지 상대경로 저장
const handleAddImages = (event) => {
const imageLists = event.target.files;
let imageUrlLists = [...showImages];
for (let i = 0; i < imageLists.length; i++) {
const currentImageUrl = URL.createObjectURL(imageLists[i]);
imageUrlLists.push(currentImageUrl);
}
if (imageUrlLists.length > 10) {
imageUrlLists = imageUrlLists.slice(0, 10);
}
setShowImages(imageUrlLists);
};
// X버튼 클릭 시 이미지 삭제
const handleDeleteImage = (id) => {
setShowImages(showImages.filter((_, index) => index !== id));
};
return (
<div className={classes.addPicture}>
<label htmlFor="input-file" className={classes.addButton} onChange={handleAddImages}>
<input type="file" id="input-file" multiple className={classes.addButton} />
<Plus fill="#646F7C" />
<span>사진추가</span>
</label>
// 저장해둔 이미지들을 순회하면서 화면에 이미지 출력
{showImages.map((image, id) => (
<div className={classes.imageContainer} key={id}>
<img src={image} alt={`${image}-${id}`} />
<Delete onClick={() => handleDeleteImage(id)} />
</div>
))}
</div>
);
};
틀린 부분이 있거나 보충해야 할 내용이 있다면 댓글이나 DM(sungstonemin)으로 알려주시면 감사하겠습니다😄
좋은 정보 공유주셔서 감사합니다. createObjectURL을 통해 만들어진 url은 해당 브라우저가 존재한 상태에서 무효화시키지 않으면 메모리 누수가 일어난다고 하네요! 보시는 분들 하단 링크 참고하셔도 좋을 것 같아요 :)
https://kyounghwan01.github.io/blog/JS/JSbasic/Blob-url/#createobjecturl
createObjectURL 꿀 정보네요 👍