FileReader로 이미지 미리보기

이주희·2022년 5월 1일
0

JavaScript

목록 보기
27/49

이미지 성능 최적화
[기존 방식]
[미리보기]
[Promise.all로 한번에 변환하기]
[LazyLoad, PreLoad, 이미지 관련 라이브러리]


이 내용은 이미지 업로드에만 국한되는 내용이 아니다!
다른 기능에도 잘 활용해보자~!

기존의 이미지 업로드 방식

1) input태그에서 파일을 올리면(onChange가 일어나면) uploadFile API를 요청했다.
2) 백엔드에서는 그 파일을 Storage에 저장하고 url을 받아와서 API 응답으로 프론트에 돌려줬다.
3) 받은 url을 img 태그를 이용해서 미리보기를 보여줬다.
4) createBoard API에 url을 보내서 등록했다.

문제점 1: 미리보기에 시간이 걸린다.

uploadFile을 통해 파일을 업로드하고 url을 받아오는 데 시간이 걸렸다.

문제점 2: 불필요한 이미지(찌꺼기)가 Storage에 저장된다.

이미지를 선택했다가 등록을 하지 않더라도 Storae에 선택했던 이미지가 모두 저장되어 불필요한 Storage 비용이 발생된다.


해결 방법 👉🏻 JS로 임시로 사용할 url을 만든다.

  • JS만으로, Storage에 저장하지 않고 미리보기만을 위한 url을 생성할 수 있다. (이걸 DB로 보내는 건 ㄴㄴ)
  • uploadFile API는 게시글을 등록할 때 요청한다. (Storage에 찌꺼기가 쌓일 일이 없다.)

FileReader로 이미지 임시 주소 만들기

  1. FileReader를 통해 미리보기를 위한 임시 주소를 생성한다.
  2. 등록하는 화면에서는 1의 url을 이용해서 선택한 이미지를 보여준다.
  3. 게시글을 등록할 때(DB로 보낼 때) 파일uploadFile API로 보내서 간단한 url로 변환한다.
  4. 변환한 간단한 url을 DB로 보내서(createBoard) 게시글을 등록한다.
import { gql, useMutation } from "@apollo/client";
import { ChangeEvent, useState } from "react";

const CREATE_BOARD = gql`
  mutation createBoard($createBoardInput: CreateBoardInput!) {
    createBoard(createBoardInput: $createBoardInput) {
      _id
    }
  }
`;
const UPLOAD_FILE = gql`
  mutation uploadFile($file: Upload!) {
    uploadFile(file: $file) {
      url
    }
  }
`;
export default function ImageUploadPreviewPage() {
  const [file1, setFile1] = useState<File>();
  const [imageUrl, setImageUrl] = useState("");
  const [uploadFile] = useMutation(UPLOAD_FILE);
  const [createBoard] = useMutation(CREATE_BOARD);

  const onChangeFile = (e: ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0]; // 파일이 없을 수도 있으니 옵셔널 체이닝 사용(배열도 옵셔널체이닝을 쓸 수 있다.)
    if (!file) {
      // 파일 검증
      alert("파일이 없습니다.");
      return;
    }
    const fileReader = new FileReader(); // JS의 내장기능이다.
    fileReader.readAsDataURL(file); // file은 Blob(이진 형태의 큰 데이터)
    // blob 타입의 file을 url 형태로 만든다. 이 url을 DB에 바로 저장할 수 있지만 크기가 너무 크니까 ㄴㄴ..

    fileReader.onload = (data) => {
      // file을 다 읽으면 읽어진 결과물이 data로 들어오고 이 함수가 실행된다.

      if (typeof data.target?.result === "string") {
        // useState 초기값으로 string("")을 넣었기 때문에 이 if문을 넣지 않으면 undefined나 null일 경우 때문에 에러가 난다.

        console.log(data.target?.result); // file을 url 형태로 읽은 결과물이다.
        setImageUrl(data.target?.result); // 미리보기를 위한 *임시 url* (Blob 형태)
        setFile1(file); // uploadFile API에 보내기 위한 *File*
      }
    };
  };

  const onClickSubmit = async () => {
    // 1) uploadFile에 *file*을 보내서 업로드용 url을 얻어낸다.
    const result1 = await uploadFile({ variables: { file: file1 } });
    const imageUrl = result1.data.uploadFile.url;

    // 2) 게시글을 등록한다.
    const result2 = await createBoard({
      variables: {
        createBoardInput: {
          writer: "juhee",
          title: "Ttttttttitle",
          contents: "Connnnnntents",
          password: "1234",
          images: [imageUrl],
        },
      },
    });
    console.log(result2.data.createBoard._id);
  };

  return (
    <div>
      <input type="file" onChange={onChangeFile} />
      <img src={imageUrl} />
      {/* 미리보기 주소가 들어간다. */}
      <button onClick={onClickSubmit}>게시글 등록하기</button>
    </div>
  );
}

FileReader 실행 결과

  • 이상태로 DB에 바로 넣으면 사이즈가 너무 커서 DB 용량이 금방 찰 것이다ㅜㅜ
    그러므로, 이 url은 Storage에 보내고 응답으로 받은 url을 우리 DB에 저장하도록 하자!!
profile
🍓e-juhee.tistory.com 👈🏻 이사중

0개의 댓글