[TIL] 9월 28,29일 ImageUpload(2)

기록하며 공부하자·2021년 9월 30일
0

Image를 게시글에 성공적으로 등록하였지만 뭔가 부족한 느낌이다.

Image는 text보다 용량도 크기때문에 용량제한도 하며 스토리지 저장소를 사용한다.

기존에 Image 등록방식 태그를 다시 살펴보면 다음과 같다.

 <img src={`https://storage.googleapis.com/${imageUrl}`} />

위 태그를 보면 미리보기 상태에서도 스토리지 서버에 저장하는것을 알수 있다.

만약 게시글을 등록하지 않고 미리보기 상태에서 뒤로가기를 클릭한다고 하면 쓸모 없는 이미지들이 스토리지에 저장되게 되고 해당 이미지는 영원히 사용하지 않게 된다.

스토리지에 이미지가 계속 쌓이면 비용도 많이내고 사이트 이용 속도에도 좋지 않은 영향을 미치게 된다.

임시 이미지URL 생성

위와 같은 상황을 피하려면 이미지를 실제 등록할때만 서버에 업데이트 하면 된다.
이미지 미리보기는 컴퓨터에 저장된 내용을 기반으로한 임시 URL생성으로 보여주면 된다.
이미 컴퓨터에 저장된 항목을 보여주는 것이기에 속도도 훨씬 빠르게 된다.

기존에 작성했던 코드중에서 몇가지 변경되어야 한다.

기존 이미지 업로드 코드

<img src={`https://storage.googleapis.com/${imageUrl}`} />

임시 url 방식의 이미지 업로드 코드

<img src={imageUrl} />

기존에 앞에 붙어있었던 스토리지 주소가 없어진 모습이다.

임시보기 기능을 이용해 업로드 전에는 임시주소를 보여주고, 실제 등록할때는 주소를 다시 원래상태로 변환해서 서버에 저장시킨다.

실제 등록된 물품을 볼때는 다시 스토리지 주소를 붙인 내용을 보여주게하는 방식이다.

전체코드(임시 URL 방식)

import { useMutation, gql } from "@apollo/client";
import { useRef, useState } from "react";
import { fileValidation } from "../../../src/commons/libraries/validation";
const UPLOAD_FILE = gql`
  mutation uploadFile($file: Upload!) {
    uploadFile(file: $file) {
      url
    }
  }
`;
const CREATE_BOARD = gql`
  mutation createBoard($createBoardInput: CreateBoardInput!) {
    createBoard(createBoardInput: $createBoardInput) {
      _id
    }
  }
`;
export default function ImageUpgradePage() {
  const fileRef = useRef<HTMLInputElement>();
  const [uploadFile] = useMutation(UPLOAD_FILE);
  const [createboard] = useMutation(CREATE_BOARD);
  const [imageUrl, setImageUrl] = useState("");
  const [myFile, setMyFile] = useState("");
  async function onChangeFile(event) {
    const file = event.target.files[0];
    if (!fileValidation(file)) return;
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file);
    fileReader.onload = (data) => {
      setImageUrl(data.target.result);
      setMyFile(file);
      console.log(myFile);
    };
    // const result = await uploadFile({variables: {file: myFile,},});
    // console.log(result.data.uploadFile.url);
    // setImageUrl(result.data.uploadFile.url);
  }
  function onClickUpload() {
    fileRef.current?.click();
  }
  const [myWriter, setMyWriter] = useState();
  const [myPassword, setMyPassword] = useState();
  const [myTitle, setMyTitle] = useState();
  const [myContents, setMyContents] = useState();
  function onChangeWriter(event) {
    setMyWriter(event.target.value);
  }
  function onChangePassword(event) {
    setMyPassword(event.target.value);
  }
  function onChangeTitle(event) {
    setMyTitle(event.target.value);
  }
  function onChagneContents(event) {
    setMyContents(event.target.value);
  }
  async function onClickSubmit() {
    const start = performance.now();
    const result = await Promise.all([
      uploadFile({ variables: { file: myFile } }),
      uploadFile({ variables: { file: myFile } }),
      uploadFile({ variables: { file: myFile } }),
      uploadFile({ variables: { file: myFile } }),
    ]);
    const end = performance.now();
    console.log(end - start);
    const urls = result.map((el) => el.data.uploadFile.url);
    createboard({
      variables: {
        createBoardInput: {
          writer: myWriter,
          password: myPassword,
          title: myTitle,
          contents: myContents,
          images: urls,
        },
      },
    });
  }
  return (
    <>
      작성자: <input type="text" onChange={onChangeWriter} />
      <br />
      비밀번호 : <input type="password" onChange={onChangePassword} />
      <br />
      제목 : <input type="text" onChange={onChangeTitle} />
      <br />
      내용 :
      <input type="text" onChange={onChagneContents} />
      <br />
      <input
        type="file"
        ref={fileRef}
        style={{ display: "none" }}
        onChange={onChangeFile}
      />
      <br />
      <img src={imageUrl} />
      <br />
      <div
        style={{ width: "200px", height: "200px", backgroundColor: "yellow" }}
        onClick={onClickUpload}

        이미지선택
      </div>
      <button onClick={onClickSubmit}>저장하기</button>
    </>
  );
}

임시 url을 생성하고, 물품등록시 다시 url 주소를 반환하는 부분이 핵심이다.

임시 url 생성부분 코드

async function onChangeFile(event) {
    const file = event.target.files[0];
    if (!fileValidation(file)) return;
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file);
    fileReader.onload = (data) => {
      setImageUrl(data.target.result);
      setMyFile(file);
      console.log(myFile);
    };

const files =event.target.files[0] 부분은 input 태그의 속성중 file인 input 태그에 이미지를 첨부했을시 정보를 가져오는 역할을 한다.

console.log()로 해당부분에 대한 정보를 조회하면 다음과 같은 정보들을 얻을수 있다.

new FileReader() 기능은 파일 객체를 이용해 내용을 일고 사용자 컴퓨터에 저장하는것을 가능하게 해주는 기능이고 이 기능을 이용해서 임시URL을 뽑아낼수 있다.

예제코드

const readImage = (input) => {
		// 인풋 태그에 파일이 있는 경우
		if (input.target.files && input.target.files[0]) {
			// FileReader 인스턴스 생성
			const reader = new FileReader();
			// reader가 이미지 읽도록 하기
			reader.readAsDataURL(input.target.files[0]);
			// 이미지가 성공적으로 읽힌 경우 onload가 실행됩니다. 
			reader.onload = (e) => {
				const previewImage = document.getElementById('image');
			// 생성된 Data URL이 e.target.result에 담기게 됩니다. 
			// 그 결과를 src에 넣어주게 되면 끝입니다. 
				previewImage.src = e.target.result;
			};
		}
	};

readAsDataURL()를 사용하면 dataURL을 얻을수 있으며 () 안에 우리가 넣은 파일을 넣어주면 된다.
파일 읽기에 성공시 onload가 실행되며, 파일을 읽고 생성된 DATA URL이 target.result에 담기게 된다.

임시 URL의 형식

다보기도 힘든 엄청나게 많은양의 url이 나왔는데 이 url을 서버에 업로드할수는 없다.
업로드 한다고해도 스토리지 주소와 결합해도 이미지 출력이 불가능하다.
이미지를 실제 업로드할때 원래주소를 업로드 시킨다.

PromiseAll

보통 이미지 업로드를 생각하면 한장만 올리는경우도 있지만 여러장을 한꺼번에 올리기도 한다.

이미지를 올릴때 미리보기를하고 그때 async await가 필요하게 된다.

만약 10장을 한꺼번에 올린다고하면? 업로드를 동기방식으로 처리하기 때문에 10번을 기다려야 한다.

이용자 입장에서는 불편함을 느낀다.

PromiseAll은 이미지를 모두 한번에 요청하기 때문에 보다 빠르게 업로드 될수있다.

예제코드

  async function onClickSubmit() {
    const start = performance.now();
    const result = await Promise.all([
      uploadFile({ variables: { file: myFile } }),
      uploadFile({ variables: { file: myFile } }),
      uploadFile({ variables: { file: myFile } }),
      uploadFile({ variables: { file: myFile } }),
    ]);
    const end = performance.now();
    console.log(end - start);
    const urls = result.map((el) => el.data.uploadFile.url);
    createboard({
      variables: {
        createBoardInput: {
          writer: myWriter,
          password: myPassword,
          title: myTitle,
          contents: myContents,
          images: urls,
        },
      },
    });
  }

promisAll을 이용해서 한번에 여러장 이미지를 등록한다.

등록한후 map을통해 urls라는 배열에 담아주고 그 배열을 variablse의 value값으로 넣어주면 된다.

profile
프론트엔드 개발자 입니다.

0개의 댓글