본문 바로가기
카테고리 없음

이미지 등록 프로세스, useRef (복습 19일차)

by 제이엠_ 2022. 4. 27.

파이어베이스 실습 리뷰

import { useEffect, useState } from "react";
import MyfirebaseListUI from "./MyfirebaseList.presenter";
import {
  collection,
  getFirestore,
  getDocs,
  DocumentData,
} from "firebase/firestore/lite";
import { firebaseApp } from "../../../../../pages/_app";
import { useRouter } from "next/router";

export default function MyfirebaseList() {
  const router = useRouter();
  const [dataBoards, setDataBoards] = useState<DocumentData[]>([]);

  useEffect(() => {
    async function fetchBoards() {
      const board = collection(getFirestore(firebaseApp), "board");
      const result = await getDocs(board);
      const boards = result.docs.map((el) => el.data());
      setDataBoards(boards);
    }
    fetchBoards();
  }, []);

  function onClickMoveToBoardNew() {
    router.push("/myfirebase/new");
  }

  return (
    <MyfirebaseListUI
      dataBoards={dataBoards}
      onClickMoveToBoardNew={onClickMoveToBoardNew}
    />
  );
}

데이터를 불러오는데 기존의 graphQL은 useQuery를 사용하여 바로 불러와서 쓰는데 rest API를 useQuery처럼 쓸 수 있는 것이 reactQuery이다. 현재 추세인 만큼 배워볼 필요가 있겠다. 실습에서는 reactQuery가 아닌 오픈API 데이터를 불러온 것처럼 useEffect를 사용하여 데이터를 불러오고 useState를 활용해 데이터를 채워준다. 

 

Image-Process & Cloud-Storage, 이미지 저장 과정

출처 : (주)딩코 코드캠프

이미지 저장 storage 또한 컴퓨터이며 여러 컴퓨터들을 연결시켜 놓은 큰 용량을 담을 수 있는 데이터베이스라고 보면 된다. uploadFile 이라는 api가 있을때, 파일을 선택하고 uploadFile을 요청하게 될 경우, backend에서 storage로 파일을 전송하게 된다. storage에서는 backend로 이미지 주소를 넘겨주게 되며 이 주소를 다시 front에게 주게 된다. 우리는 이 주소를 가지고 api 요청 시, image에 대한 주소를 보낼 수 있게 되는 것 이며, 이 정보들을 DataBase에 저장하게 된다. imgFile에 접근할 수 있는 주소만 저장되고 파일은 없다.

 

yarn add apollo-upload-client
yarn add @types/apollo-upload-client -—dev

업로드를 사용하기 위해서 apollo-upload-client를 설치하자.

 

  const uploadLink = createUploadLink({
    uri: "링크",
    headers: { Authorization: `Bearer ${accessToken}` },
    // credentials: "include",
  });

const client = new ApolloClient({
    link: ApolloLink.from([uploadLink as unknown as ApolloLink]),
    cache: new InMemoryCache(),
    connectToDevTools: true,
  });

그리고 _app.tsx에서 설정을 해준다.

 

import { useMutation, gql } from "@apollo/client";
import { ChangeEvent, useState } from "react";
import {
  IMutation,
  IMutationUploadFileArgs,
} from "../../src/commons/types/generated/types";

const UPLOAD_FILE = gql`
  mutation uploadFile($file: Upload!) {
    uploadFile(file: $file) {
      url
    }
  }
`;

export default function ImageUploadPage() {
  const [image, setImage] = useState("");
  const [uploadFile] = useMutation<
    Pick<IMutation, "uploadFile">,
    IMutationUploadFileArgs
  >(UPLOAD_FILE);

  const onChangeFile = async (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    console.log(file);

    try {
      const result = await uploadFile({ variables: { file } });
      console.log(result.data?.uploadFile.url);

      setImage(result.data?.uploadFile.url || "");
    } catch (error) {
      if (error instanceof Error) alert(error.message);
    }
  };

  return (
    <div>
      <input type="file" onChange={onChangeFile} />
      <img src={`https://storage.googleapis.com/${image}`} />
    </div>
  );
}

console.log(file)의 결과는 아래와 같다.

lastModified: 1650779031716

lastModifiedDate: Sun Apr 24 2022 14:43:51 GMT+0900 (한국 표준시) {}

name: "이미지 파일명"

size: 116063

type: "image/jpeg"

webkitRelativePath: ""

 

이미지의 src에는 http://storage.googleapis.com/ 가 있는데 이 주소도 저장하도록 할 수 있지만 cloud가 바뀔 경우도 있기에 유지보수 측면에서 따로 빼고 이미지 파일명만 데이터로 가지고 있는 것이 나은 방법이 될 수 있다.

 

Validation

export const checkFileValidation = (file?: File) => {
  if (!file?.size) {
    alert("파일이 없습니다.");
    return false;
  }

  if (file.size > 5 * 1024 * 1024) {
    alert("파일 용량이 너무 큽니다.(제한: 5MB)");
    return false;
  }

  if (!file.type.includes("jpeg") && !file.type.includes("png")) {
    alert("jpeg 파일 또는 png 파일만 업로드 가능합니다!!!");
    return false;
  }

  return true;
};

이미지를 올리는 것에 있어서 파일이 없거나 파일 용량이 크거나, 확장자가 잘못된 것에 대해서 예외처리를 해주는 것이 사이트의 안정성을 높인다. 그에 있어서 함수를 구현해주는데 if문으로 예외를 먼저 처리해주는 것이 early exit 패턴이라 부른다.

 

이미지 업로드의 경우 하나의 페이지에서 사용하는 것이 아니라 다른 페이지에서 사용할 수 있기 때문에 하나의 함수로 만들어서 임포트해서 쓰는 것이 더 효율적이다. src/commons/libraries 에 넣어두자.

 

이미지 업로드 버튼 새로 꾸미기(feat. useRef)

import { useMutation, gql } from "@apollo/client";
import { ChangeEvent, useRef, useState } from "react";
import { checkFileValidation } from "../../src/commons/libraries/validation";
import {
  IMutation,
  IMutationUploadFileArgs,
} from "../../src/commons/types/generated/types";

const UPLOAD_FILE = gql`
  mutation uploadFile($file: Upload!) {
    uploadFile(file: $file) {
      url
    }
  }
`;

export default function ImageValidationPage() {
  const fileRef = useRef<HTMLInputElement>(null);

  const [image, setImage] = useState("");
  const [uploadFile] = useMutation<
    Pick<IMutation, "uploadFile">,
    IMutationUploadFileArgs
  >(UPLOAD_FILE);

  const onChangeFile = async (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    console.log(file);

    const isValid = checkFileValidation(file);
    if (!isValid) return;

    try {
      const result = await uploadFile({ variables: { file } });
      console.log(result.data?.uploadFile.url);

      setImage(result.data?.uploadFile.url || "");
    } catch (error) {
      alert(error.message);
    }
  };

  const onClickImage = () => {
    fileRef.current?.click();
  };

  return (
    <div>
      <div
        style={{ width: "50px", height: "50px", backgroundColor: "gray" }}
        onClick={onClickImage}
      >
        이미지선택
      </div>
      <img src={`https://storage.googleapis.com/${image}`} />

      <input
        style={{ display: "none" }}
        type="file"
        ref={fileRef}
        onChange={onChangeFile}
      />
    </div>
  );
}

useRef를 input과 연결시키고 input 태그는 스타일에 display : none 으로 숨겨준다. 그리고 div를 클릭하면 onClickImage 함수를 실행시켜서 input태그를 클릭하게 함으로써 나만의 이미지 업로드 버튼을 만들 수 있다.

댓글