이미지 최적화(업로드)-3

박경찬·2022년 9월 5일
0

이번엔 드디어 파일 업로드 최적화를 해보려고한다.

필자는 파일3개를 업로드 할거다!!

이전에 작성했던 state부터 확인보자.☝🏼

  const [fileupload, setFileUpload] = useState<File>();

나는 분명 3개의 공간을 만든다고 했다. 그렇지만 1개의 파일만 올릴때는? 이때 비어있는? 남아있는? 2개의 indexundefined 을 반환한다.

그러면 ?? state에서는 어떻게 작성을 해줘야 할까 ?💡

이때 연산자를 활용해 작성해주면된다. 주로 조건문에서 사용하는 또는 | 연산자를 사용하면 간단하게 해결할수 있다.

  const [fileuploads, setFileUploads] = useState<(File | undefined)[]>([
    undefined,
    undefined,
    undefined,
  ]);

File 이나 undefined 가 들어있는 배열을 만들어주면된다.

다음 할일은?! state를 보면 첫번째, 두번째 ,세번째 index를 만들어 줬으니 input 태그와 img 태그에 들어가는 함수에도 index를 넣어줘야한다.

      <input type="file" onChange={onChangeFile(0)} />
      <input type="file" onChange={onChangeFile(1)} />
      <input type="file" onChange={onChangeFile(2)} />
  	  <img src={imageUrls[0]} />
      <img src={imageUrls[1]} />
      <img src={imageUrls[2]} />

현재 함수는 인자값을 따로 받지 않고 있기 때문에 함수도 변경해줘야 한다.

기존 함수

  const onChangeFile = (e: ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0]; 
    if (!file) {
      alert("파일이 없습니다");
      return;
    }

변경된 함수

  const onChangeFile =
    (number: number) => (e: ChangeEvent<HTMLInputElement>) => {
      const file = e.target.files?.[0]; 
      if (!file) {
        alert("파일이 없습니다");
        return;
      }

url 3개 받아야하니까! 배열로 만들어주자

변경전

  const [imageUrl, setImageUrl] = useState("");

변경후

  const [imageUrl, setImageUrl] = useState(["","",""]);

기존 함수 내용도 변경해줘야한다.

변경전

  const onChangeFile = (e: ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0]; 
    if (!file) {
      alert("파일이 없습니다");
      return;
    }
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file); 
    fileReader.onload = (data) => {
      
      if (typeof data.target?.result === "string") {
        
        setImageUrl(data.target?.result);
        setFileUpload(file);
      }
    };
  };

변경후

const onChangeFile =
    (number: number) => (e: ChangeEvent<HTMLInputElement>) => {
      const file = e.target.files?.[0]; 
      if (!file) {
        alert("파일이 없습니다");
        return;
      }
      const fileReader = new FileReader();
      fileReader.readAsDataURL(file); 
      fileReader.onload = (data) => {
        
        if (typeof data.target?.result === "string") {
          const tempUrls = [...imageUrls];
          tempUrls[number] = data.target?.result;
          const tempFiles = [...fileuploads];
          tempFiles[number] = file;
          setImageUrls(tempUrls);
          setFileUploads(tempFiles);
        }
      };
    };

위 내용에 중요하게 보고 넘어가야하는 부분은 얕은 복사를 활용하는거다.

얕은 복사는 원본을 참조하게끔 주소를 가져오는 것이다

const tempUrls = [...imageUrls] //스프레드연산자

url를 얻는 과정부터 보자

const tempUrls = [...imageUrls];
	  tempUrls[number] = data.target?.result;

tempUrls[number]numbers는 배열에 index를 의미한다. 기존 이미지에서 number에 해당하는 indexdata.target?.result 으로 바꿔 주면된다.

tempUrls 는 기존 이미지 url들 중에서 3개의 박스가 있는데
선택한 index만 변경한다.

첫번째 index를 선택했다면 number 에 0이 들어갈꺼고 두번째 index를 선택하면 1이 들어가고 세번째 index 선택하면 2가 들어간다.

여기서 index를 구분해줄수 있는건 위 내용중 함수를 보면 알수 있다.

const onChangeFile =
    (number: number) => (e: ChangeEvent<HTMLInputElement>)

함수의 인자값을 보면 number라고 확인되는데 추가된 태그에도 숫자값을 인자로 넣어 구분해줬기 때문에 구분이 가능하다.

  	  <img src={imageUrls[0]} />
      <img src={imageUrls[1]} />
      <img src={imageUrls[2]} />

이제 fileUpload를 살펴보자.

이녀석도 같은 맥락이다.
기존 파일을 가져와 현재 index에 있는거만 파일을 변경 해주면된다.파일도 똑같은 배열이기 때문이다.

  const tempFiles = [...fileuploads];
          tempFiles[number] = file;

기존에 있는 fileuploads를 가져와 변경된 index 부분만 변경해주면된다.

      <input type="file" onChange={onChangeFile(0)} />
      <input type="file" onChange={onChangeFile(1)} />
      <input type="file" onChange={onChangeFile(2)} />

여기 까지가 state에 저장되는 과정이다.

이제 마지막으로 해줄일은 실제로 백엔드로 요청하는 일만 남았다.

 await uploadFile({ variables: { file: fileuploads[0] } });
 await uploadFile({ variables: { file: fileuploads[1] } });
 await uploadFile({ variables: { file: fileuploads[2] } });

이런식으로 3번을 요청해야하는데 실제로 이렇게 요청해도 없다 코드는 상황마다 달라지기 때문에 정답은 없다.

하지만 좋은 코드는 존재한다! 저거보단 좋은 코드가 맞을거라고 생각한다..

필자는 mapPromise.all를 사용하려 한다.

Promise.all(
	
  [](x)
  
)

Promise.all안으로 map을 사용하기 때문에 굳이 [] 작성하지 않아도 된다.

map을 사용하기 때문에 []을 사용하지 않는것이지 Promis.all은 무조건 배열이 들어가 있어야 한다. Promise들을 기다려 줘야 하기 때문이다.

map을 사용해보자🧐

이전 포스팅에 배열로 url를 담아줬었다. 잠깐 작성했던 코드를 보자.🕵🏻‍♂️

    const result = await Promise.all(
      ["https://dog1.jpg", "https://dog2.jpg", "https://dog3.jpg"].map((el) => {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve(el);
          }, 3000);
        });
      })
    );

url3개를 배열로 만들었지만 이번엔 map을 사용하여 코드를 만들어보자✏️

방법은 3개정도?? 다른 사람들은 나보다 더 있을수도 있다.

첫번째

    fileuploads.map((el) => {
        if (el) {
          return uploadFile({ variables: { file: el } });
        } else {
          return undefined;
        }
      });

el 은 실제로 업로드 하는 파일이다.

업로드 하는 파일이 있을수도 있고 없을수도 있다 무조건 파일3개를 채워서 업로드 하는건 아니다. 그러므로 파일이 있을때 조건문이 실행된다.

Promise.all은 Promise가 들어가야하는데 undefined이 있으면 실제로 무시한다.

두번째

return el ? uploadFile({ variables: { file: el } }) : undefined;

결국 el이 존재하면 실행하고 없으면 undefined이기 때문에 3항연산자를 사용해주면 된다.

세번째

어차피 첫시작은 undifined라서&& 사용할수도 있다.

return el && uploadFile({ variables: { file: el } });

실제로 필자가 사용할 코드이다.

const results = await Promise.all(
  fileuploads.map((el) => el && uploadFile({ 
    variables: { file: el } }))
);

results안에는 각각의 데이터들이 배열로 들어가 있다.

이제는 여기에 url만 뽑아서 백엔드로 요청해야한다.

   const resultUrls = results.map((el) =>
        el?.data ? el?.data?.uploadFile.url : ""
      );

url이 없을수도 있다. url 이 있으면 return 해주고 없을때는 빈문자열을 넣어주면된다.

실제 완성된 코드.

  const onClickSubmit = async () => {
    try {
      const results = await Promise.all(
        fileuploads.map((el) => el && uploadFile({ 
          variables: { file: el } }))
      );
      const resultUrls = results.map((el) =>
        el?.data ? el?.data?.uploadFile.url : ""
      );

      const result = await createBoard({
        variables: {
          createBoardInput: {
            writer: "Chan",
            title: "image",
            contents: "이미지 업로드",
            images: resultUrls,
          },
        },
      });
      console.log(result.data?.createBoard._id);
      alert("생성완료");
    } catch (e: any) {
      Modal.error({ content: e.message });
    }
  };

이미지 최적화 하는 방법은...실제로 프로젝트 하면서 사용했던 코드이다. 누군가에게 도움이 되기를 바라면서 포스팅 했지만 이글을 보고 이해할수 있다면 당신은... 개발하기 위해 태어난 사람이라고 생각한다..

복습하면서 당시 배웠던 내용을 필기 해놔서 다행이긴 한데 그래도 기억이 가물가물하다.. 하하 완전히 내머릿속에서 지워지기 전에 남겨본다..

0개의 댓글