리뷰 작성기능과 수정기능을 붙이면서 필요했던 기능 중 하나는
등록 할 이미지, 등록 된 이미지를 유저에게 미리 보여줘야 했다.
수정의 경우에 아래의 조건이 필요했는데
1. 이미 등록된 이미지 제거
2. 새로 등록 될 이미지
수정중에 유저가 취소를 위해 뒤로가기를 했을 때 리뷰의 데이터가 수정이 안되어야 했기 때문에 기존 값에서 새롭게 등록 할 이미지를 우선적으로 보여줘야 했다.
기능을 구현하면서 작성했던 내용을 토대로 정리하려고 한다.
요구사항
- 등록 버튼이 눌리기 전까지는 유저가 선택한 이미지만 화면에 렌더링한다
- 유저가 선택한 이미지 저장 후 조회 시 보여준다
서버에 data를 body로 FormData 형태의 값을 보내주기로 했다.
headers Content-type의 타입을 multipart/form-data로 전달해주었다.
작성하다보니 궁금해서 찾아보니까 아래와 같은 특징도 있다.
headers: {
'Content-Type': 'multipart/form-data',
};
FormData는 여러개의 메서드를 가지고 있는데 그 중에 append
메서드를 사용해서 name과 value를 가진 폼 필드를 추가 해 줄 수 있다.
나같은 경우에는 전달해줄 data에 image를 forEach 메서드를 사용해 append 해주었는데,
폼은 name이 같은 필드 여러 개를 허용하기 때문에 append 메서드를 여러 번 호출하여 같은 필드를 계속 추가해도 된다.
uploadImages.imageFiles.forEach((image) => form.append('images', image));
const [uploadImages, setUploadImages] = useState({
imageFiles: [],
imageUrls: [],
});
const handleImageUpload = (e) => {
if (!e.target.files) return; // a
const files = e.target.files;
const fileArray = Array.from(files); //b
//c
const newImages = Array.from(files, (file) => URL.createObjectURL(file));
setUploadImages({
imageFiles: [...uploadImages.imageFiles, ...fileArray],
imageUrls: [...uploadImages.imageUrls, ...newImages],
});
};
const handleDeleteImage = (id: number) => {
setUploadImages({
imageFiles: uploadImages.imageFiles.filter((_, index) => index !== id),
imageUrls: uploadImages.imageUrls.filter((_, index) => index !== id),
});
};
file들을 가지고 있을 input은 display:none
css 속성을 전달해줘서 보이지 않도록 하고, 유저가 클릭 할 UI는 label
로 만들어 유저에게 보여준다.
{uploadImages.imageUrls.map((url, idx) => <UploadedImage/> )}
//보이지 않는 파일 업로드 인풋
<Input
id='images'
type='file'
multiple
accept='image/*'
/>
//input Id와 htmlFor 연결
<ImageUploadUI id='images' htmlFor='images'/>
type=file
mulitple
accept=image/*
저장하기 이전에 유저에게 보여줄 url을 가지고 있을 State 생성
초기값은 (서버에)이미 등록되어 있는 UrlList
const [images, setImages] = useState<{ file: File[]; blob: string[] }>({
file: [],
blob: imageUrlList,
});
새롭게 이미지를 전달 받은 경우에는
1. 위에 선언해놓은 기존 urlList의 blob값을 저장하고
2. 새 데이터의 files 가공해 배열로 만들어 준 뒤 url 생성 , 파일데이터 로
이미지는 배열의 맨 앞에 추가해주고
새롭게 업로드 할 file은 배열로 만들어준 뒤 state에 저장해 놓은 뒤
업로드 진행할 때 form.append
값으로 image 데이터를 추가해준다
images
state로 서버에 이미지 업로드 요청 setUpdateReviewState((prev) => {
return { ...prev, imageUrlList: [...prev.imageUrlList, ...data] };
});
const handleImageUpload = (e) => {
const imageBlobs = [...images.blob];
const imageFile = Array.from(files).map((file) => {
const imageUrl = window.URL.createObjectURL(file); // blob:http://localhost:3000/iamge 임시주소
imageBlobs.unshift(imageUrl);
return file; //file 데이터
});
setImages({ file: imageFile, blob: imageBlobs });
};
처음 생성때에도 여러개의 이미지를 어떻게 보여주고 값을 어떻게 저장해야 할 지에 대해서 많이 헤맸었다.
초기에는 file과 image를 가진 값을 각각의 state로 구현하였다가,
결국 두 값다 하나의 files라는 값에서 얻어와지는 값이기 때문에 분리하는 것보다는
결합성을 위해 다시 하나의 state로 합치게 되었다.