input으로 이미지 미리보기 설정하기 (+TypeScript)

쏘뽀끼·2024년 8월 8일

Next.js

목록 보기
15/18

이미지 미리보기 기능을 구현해 보았다.

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.onlaodFileReader가 파일을 성공적으로 읽었을 때 호출되는 콜백 함수이다.

  • 이 함수 내에서 파일 읽기 작업이 완료되었음을 확인하고, 이후의 처리를 수행한다.



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>
      </>
    );
  }

0개의 댓글