이미지 미리보기 기능을 구현해 보았다.
export default function SelectProfile(){
const[postImage, setPostImage]= useState<File[]>([]);
const [previewImg,setPreviewImg] =useState<string[]>([]);
return(//
)
}
일단 올릴 이미지 state하나, 미리보기 이미지 state하나를 만들었다.
const uploadFile =(e:React.ChangeEvent<HTMLInputElement>)=>{
cont file = e.target.files;
}
그 다음 input에 들어갈 함수를 만들었다.
파일을 선택하게 된다면 e.target.files로 인해 file 변수에 선택한 파일들을 담고 있는 FileList 객체를 담을 수 있다.
if (file) {
const filesArray = Array.from(file);
setPostImage((prevImages) => [...prevImages, ...filesArray]);
const uploadFile =(e:React.ChangeEvent<HTMLInputElement>)=>{
cont file = e.target.files;
const fileUrls:string[]=[];
const fileReaders: Promise<void>[]=[];
}
만약 지정된 파일이 있다면, 선택된 파일들이 배열 형태로 저장되도록 Array.from을 사용해 fileArray에 배열로 담아줬다.
fileUrls는 파일 읽기 결과 데이터를 저장할 배열,
fileReaders는 파일 읽기 작업을 나타내는 Promise 객체들을 저장할 배열이다.
filesArray.forEach((file)=>{
const fileRead = new FileReader();
const fileReaderPromise = new Promise<void>((resolve)=>{
fileRead.onload = ()=>{
if(fileRead.result){
fileUrls.push(fileRead.result as string);
resolve();
}
};
fileRead.readAsDataURL(file);
});
fileReaders.push(fileReaderPromise);
});
uploadFile함수 안에 이어서 이렇게 작성해줬다.
하나씩 집어보면,
지정된 파일들을 각가
const fileRead = new FileReader();
각 파일에 대해 FileReader객체를 생성한다.
const fileReaderPromise = new Promise<void>
new Promise<void>는 새로운 Promise객체를 생성한다.Promise는 비동기 작업의 완료를 나타내는 객체로 작업이 완료되었을 때 resolve함수를 호출한다. <void>는 이 Promise가 반환할 값이 없음을 의미한다. fileRead.onload=()=>{
fileRead.onlaod는 FileReader가 파일을 성공적으로 읽었을 때 호출되는 콜백 함수이다.
이 함수 내에서 파일 읽기 작업이 완료되었음을 확인하고, 이후의 처리를 수행한다.
if(fileRead.result){
fileUrls.push(fileRead.result as string);
}
fileRead.result는 파일 읽기 작업의 결과를 담고 있다. 이 값은 string타입으로 변환된 파일 데이터이다. fileUrls.push(fileRead.result as string)는 읽어들인 파일 데이터를 fileUrls 배열에 추가한다. resolve();
resolve()는 Promise가 완료되었음을 나타낸다. Promise를 사용하는 다른 부분에서는 이 resolve호출을 통해 비동기 작업이 완료되었음을 알 수 있다.
이 단계에서는 파일 읽기 작업이 끝나으므로 resolve를 호출하여 Promise를 완료하고, 후속 작업이 진행될 수 있어야 한다.
fileRead.readAsDataURL(file);
fileRead.readAsDataURL는 파일을 데이터 URL로 읽기 시작한다. 이 과정이 완료되면 onload콜백히 호출된다. 그 다음
Promise.all(fileReaders).then(()=>{
setPreviewImg((prevUrls)=>[...prevUrls,...fileUrls]);
});
이렇게 써줬다.
Promise.all(fileReaders)
fileReaders배열에는 여러 개의 Promise객체가 들어있다. 각 Promsie는 파일을 비동기적으로 읽는 작업을 나타낸다. Promise.all은 배열 안의 모든 Promise가 완료될 때까지 대기한다. 즉, 모든 파일의 읽기 작업이 완료될 때까지 기다린다. 모든 작업이 완료 후
.then(()=>{
setPreviewImg((prevUrls)=> [...prevUrls, ...fileUrls]);
})
Promise.all이 반환하는 Promise가 완료되면, .then()메서드에 정의된 콜백 함수가 실행된다.
콜백 함수에는 모든 파일의 읽기 작업이 완료된 후 수행할 작업을 정의한다.
setPreviewImg((prevUrls) => [...prevUrls, ...fileUrls]);
setPreviwImg는 React의 상태 업데이트 함수이다. 현재 상태 (prevUrls)에 새로운 파일 URL 배열(fileUrls)를 추가하여 상태를 업데이트 한다.
코드는 파일 읽기 작업의 결과를 포함하는 fileUrls 배열을 기존의 previewImg 배열에 추가하여 상태를 새로 고친다.
export default function SelectProfile() {
const [postImage, setPostImage] = useState<File[]>([]);
const [previewImg, setPreviewImg] = useState<string[]>([]);
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const uploadFile = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files;
if (file) {
const filesArray = Array.from(file); //선택된 파일들이 배열 형태로 저장
setPostImage((prevImages) => [...prevImages, ...filesArray]); //선택된 파일들의 배열이 저장된다.
const fileUrls: string[] = []; //파일 읽기 결과 데이터를 저장할 배열
const fileReaders: Promise<void>[] = []; //파일 읽기 작업을 나타내는 Promise 객체들을 저장할 배열
filesArray.forEach((file) => {
const fileRead = new FileReader(); //각 파일에 대해 FileReader객체를 생성
const fileReaderPromise = new Promise<void>((resolve) => {
fileRead.onload = () => {
if (fileRead.result) {
fileUrls.push(fileRead.result as string); //파일 읽기 작업 완료후 fileUrls에 저장
resolve(); //resolve를 호출해 Promise를 완료한다.
}
};
fileRead.readAsDataURL(file);
});
fileReaders.push(fileReaderPromise);
});
Promise.all(fileReaders).then(() => {
//모든 파일 읽기 작업이 완료될 때까지 대기
setPreviewImg((prevUrls) => [...prevUrls, ...fileUrls]);
});
}
};
return(
<>
{postImage.length > 0 ? (
<div className="flex gap-1">
{previewImg.map((img, index) => (
<div
key={index}
className='rounded-full border w-[100px] h-[100px] relative cursor-pointer'
>
{이미지 파일이 올라갔을 때 이미지}
</div>
))}
</div>
) : (
{이미지 파일이 올라가지 않았을 때}
)}
<label className=" cursor-pointer">
<input
type="file"
multiple
accept="image/*"
onChange={uploadFile}
className=" hidden"
/>
<div className="rounded-full border w-[100px] h-[100px] relative">
<Image
src={plusIcon}
alt="plusIcon"
className="absolute inset-0 m-auto w-1/2 h-1/2 object-contain"
width={50}
height={50}
/>
</div>
</label>
</>
);
}