useUploadImage 훅
import { ChangeEvent, useRef, useState } from "react";
function useUploadImage() {
// file 타입 input을 참조하기 위한 ref
const imageInputRef = useRef<HTMLInputElement | null>(null);
// 선택된 이미지 파일들을 저장할 상태
const [imageFiles, setImageFiles] = useState<File[]>([]);
// 선택된 이미지 파일의 미리보기 URL을 저장할 상태
const [previewUrls, setPreviewUrls] = useState<string[]>([]);
// 파일 선택 시의 이벤트 핸들러
const handleFileChange = (e: ChangeEvent<HTMLInputElement>): void => {
// 선택된 파일이 있는지 확인
if (e.target.files) {
// 선택된 파일들 중에서 최대 10개까지만 처리함. 이미 선택된 파일이 있다면 그 수를 고려해 최대한 받아옴.
const filesArray = Array.from(e.target.files).slice(
0,
10 - imageFiles.length
);
// 기존의 이미지 파일들에 새로운 파일들을 추가
const newImageFiles = [...imageFiles, ...filesArray];
setImageFiles(newImageFiles);
// 미리보기 URL을 업데이트하기 위한 임시 배열
const newPreviewUrls = [...previewUrls];
// 각 파일에 대해 FileReader를 사용하여 미리보기 URL을 생성
filesArray.forEach((file) => {
const reader = new FileReader();
// 파일 읽기가 끝났을 때 실행될 콜백을 정의
reader.onloadend = () => {
// 생성된 미리보기 URL을 임시 배열에 추가
newPreviewUrls.push(reader.result as string);
// 모든 파일의 미리보기 URL이 준비되었는지 확인하고, 준비되었다면 상태를 업데이트함
if (newPreviewUrls.length === newImageFiles.length) {
setPreviewUrls(newPreviewUrls);
}
};
// 파일을 Data URL 형태로 읽음. 이 URL은 미리보기 이미지로 사용됨
reader.readAsDataURL(file);
});
}
};
// 이미지를 닫는 함수. 지정된 인덱스의 이미지를 상태에서 제거함
const handleCloseImage = (index: number): void => {
// 지정된 인덱스를 제외한 나머지 이미지 파일들을 필터링함
const newImageFiles = imageFiles.filter(
(_, fileIndex) => fileIndex !== index
);
setImageFiles(newImageFiles);
// 지정된 인덱스를 제외한 나머지 미리보기 URL들을 필터링함
const newPreviewUrls = previewUrls.filter(
(_, urlIndex) => urlIndex !== index
);
setPreviewUrls(newPreviewUrls);
};
// 이미지 업로더 버튼 클릭 시의 이벤트 핸들러. 실제 파일 입력 요소를 클릭하도록 함 (file 타입의 input은 스타일링이 불가능하기 때문...)
const handleClickImageUploader = (): void => {
imageInputRef.current?.click();
};
return {
imageInputRef,
handleClickImageUploader,
handleFileChange,
previewUrls,
handleCloseImage,
imageFiles,
};
}
export default useUploadImage;
컴포넌트 코드
// 생략...
return (
<AddProductDetailSection
label="이미지 등록"
limit={`(${previewUrls.length}/10)`}
type="image"
>
<div className={styles.container}>
<div className={styles.images_container}>
{previewUrls.length < 10 && (
<section className={styles.left_section}>
<div
onClick={handleClickImageUploader}
className={styles.add_image_wrapper}
>
<img
src="/images/Camera.png"
alt="Add more"
className={styles.camera_icon}
/>
</div>
<p className={styles.image_text}>
{imageFiles.length === 0 ? "썸네일/대표 이미지" : "추가 이미지"}
</p>
</section>
)}
<section className={styles.right_section}>
// previewUrls 배열을 기반으로 한 미리보기 렌더링
{previewUrls.map((url, i) => (
<div key={i} className={styles.image_wrapper}>
<div className={styles.image_section}>
<img
src={url}
alt={`Image preview ${i + 1}`}
className={styles.image}
/>
</div>
<div
// 미리보기 배열 중 선택된 요소 취소
onClick={() => handleCloseImage(i)}
className={styles.close_icon}
/>
<p className={styles.image_text}>
{i === 0 ? "썸네일/대표 이미지" : "추가 이미지"}
</p>
</div>
))}
</section>
<input
type="file"
onChange={handleFileChange}
ref={imageInputRef}
multiple
style={{ display: "none" }}
/>
</div>
</div>
</AddProductDetailSection>
);
}