[코드캠프]TIL_19일_이미지업로드

윤성해·2023년 4월 10일
0

프론트엔드_TIL

목록 보기
18/27
post-thumbnail

🏷️ 수업 목표

  1. 이미지 프로세스 이해
  2. 이미지 업로드 해보기
  3. HTML 기본 파일태그 숨기기
  4. 이미지 검증

1. 이미지 프로세스 이해(이미지서버, 스토리지)

이미지 프로세스를 이해하기 위해서는 storage사용을 알아야한다.
스토리지 또한 컴퓨터이고, 여러 컴퓨터를 연결시켜놓은 큰 용량을 담을 수 있는 데이터베이스이다.

위와같이 있을때, qqq에 강아지.jpg가 들어가게 된다. onchange누르면 백엔드 업로드파일 api에 들어간다. 그러면 원래는 백엔드가 데이터베이스에 저장을 해야하는데 이렇게하면 용량이 너무많이 필요하다. 그래서 컴퓨터를 빌려주는 회사들(클라우드드 프로바이더)이 등장한다. AWS,GCP,Azure 등이 있음!!

이미지 업로드


uploadFile 이라는 api가 있을때, 파일을 선택하고 uploadFile을 요청하게 될 경우, backend에서 storage로 파일을 전송하게 됩니다.

이미지 조회


우리가 이미지를 보기 위해서는 주소를 사용해 접근하게 됩니다.
storage에서는 backend로 이미지를 조회할 수 있는 이미지 주소를 넘겨주게 되고, database에 이미지 주소를 저장합니다. (파일을 저장하는 것이 아닌, 주소를 저장!)
frontend에서도 이미지 주소를 통해 이미지를 조회합니다.
(❗️ imgFile에 접근할수있는 주소만 저장, file 없음)

💡 근데 .. 사진이 어떻게 저장된다는 건가요!?

일단 디비에 blob타입으로 저장하면 저장은 되지만, 용량이 너무 크다.

요때 클라우드에서 주소 받아서 넣어진다.

2. 이미지 업로드하기

우리가 이미지 업로드에 사용할 api는 uploadFile 이다.
이미지 업로드는 uploadFile로 받아온 이미지 url을 createBoard에 넣어주면 됨. url을 받아오기 위해선 아폴로 업로드 관련 라이브러리를 설치해주기!

1) 라이브러리 설치

  1. 터미널에 yarn add apollo-upload-client 입력
  2. 나는 타입스크립트라서 아폴로 업로드의 타입스크립트도 설치 yarn add @types/apollo-upload-client --dev

2) 사용을 위한 세팅

  1. app.tsx 파일에서 세팅
// app.tsx파일
// import 추가하기
import {createUploadLink} from "apollo-upload-client"

// 세팅 함수 부분
const uplodLink = createUploadLink({
		uri : "백엔드 주소"
	})

const client = new ApolloClient({
		link : ApolloLink.from([uplodLink]),
		cache : new inMemoryCache(),
	})

진행중, 설치해두었던 라이브러리들의 버전이 맞지않아서 uploadLink쪽에 에러가 났다. package.json에서 아래와 같이 resolutions로 버전 맞춰주고 다시 yarn install해주기

3) 파일을 업로드할 수 있도록 화면그리기

input 태그의 type에 file로 지정하면 알아서 선택버튼 그려지고, 누르면 파일 선택할 수 있게 창이 나온다.

// 이미지 업로드 api 사용을 위한 쿼리 작성
const UPLOAD_FILE = gql`
  mutation uploadFile($file: Upload!) {
    uploadFile(file: $file) {
      url
    }
  }
`;
export default function ImageUploadPage(): JSX.Element {
  const [imageUrl, setImageUrl] = useState("");
  const [uploadFile] = useMutation<
    Pick<IMutation, "uploadFile">,
    IMutationUploadFileArgs
  >(UPLOAD_FILE);

  // 이미지 업로드 함수
  const onChangeFile = async (
    event: ChangeEvent<HTMLInputElement>
  ): Promise<void> => {
    const file = event.target.files?.[0]; // 배열로 들어오는 이유: <input type="file" multiple/>일때, 여러 개 업로드 가능하기 때문입니다.
    const result = await uploadFile({ variables: { file } });
		console.log(result.data?.uploadFile.url);
    setImageUrl(result.data?.uploadFile.url ?? "");
  };
  return (
    <>
      <input onChange={onChangeFile} type="file" />
      <img src={`https://storage.googleapis.com/${imageUrl}`} />
    </>
  );

이미지를 보내주고, url을 받아오는 과정 을 자세히 보도록 합시다!

  1. onChange를 통해 이미지 파일을 가지고와 file에 넣어줍니다.
  2. file을 variables에 넣어 uploadFile mutation을 날려줍니다.
  3. 이미지가 정해둔 스토리지에 들어가고, 해당 스토리지에서 url을 반환 받아 가지고 옵니다.
  4. 콘솔에 받아온 이미지 URL이 찍힙니다.
    ‼️ 해당 url의 이미지를 보고싶다면, 저장해둔 스토리지 주소의 뒤에 이미지 url을 적어주시면 됩니다. ‼️ 구글컴퓨터 주소 : http://storage.goolgeapis
    (예시)http://storage.goolgeapis.com/이미지url

3. HTML 기본 FILE 태그 숨기기

input type을 file로 하면 기본적으로 아래처럼 나오는데, 얘는 div가 아니라서 css로 버튼을 꾸미는게 어렵다. 그래서 이것을 숨기고, 예쁜 버튼을 새로 만들어준다음에 예쁜 버튼이 눌리면 숨겨진 input태그가 실행되도록 할거임!!

useRef 이용하기

HTML 태그를 선택할 때는 getElementId를 사용했다. 이렇게 해도 괜찮지만 리액트에서는 HTML 태그에 접근을 도와주는 역할을 useRef가 하고 있다.

사용방법

import { useRef } from 'react';

export default function ImageRefPage(): JSX.Element {
	const fileRef = useRef<HTMLInputElement>(null);
}

import로 useRef 가져오고, const fileRef = useRef(); 작성.
이후에 fileRef 를 태그에 연결해주면 해당 태그는 fileRef로 불러서 사용할 수 있다.

태그에 넣어주기

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

이렇게 input 태그에 ref={fileRef}을 작성하면 이제 input 태그를 fileRef을 이용해 사용할 수 있다.
그리고 나서, useRef에 있는 기능을 이용해주면 됨.
useRef에는 다양한 기능이 있지만 하나만 본다면!

const onClickImage = (): void => {
    // 기존 방식: document.getElementById("파일태그ID")?.click();
    fileRef.current?.click();
  };

onClick에 넣을 함수인데, Ref에는 current 안에 click이라는 기능이 있다. 이름 그대로 current는 fileRef에 들어온 태그를 뜻하고, 그 태그를 click하겠다는 기능.

바인딩시키기

<button onClick={onClickImage}>이미지 등록 버튼</button>

그리고 새로운 버튼을 하나 만들어서 해당 기능을 넣어주었다.

이렇게 하면 button을 클릭했을 때 fileRef.current.click();이 실행될 것이고, 그것은 우리가 useRef에 넣어두었던 input 태그를 클릭한 것과 같은 결과가 나올 것임!

조금 더 응용해서,

return (
		<>
			<div>
				<img onClick={onClickImage} style={{ width: '500px' }} id="image" />
				<input
					hidden={true}
					ref={fileRef}
					type="file"
					onChange={readImage}
				></input>
				<button onClick={onClickImage}>이미지 등록 버튼</button>
			</div>
		</>
	);

위의 코드를 보면 미리보기 이미지에도 onClickImage을 넣어주었다.
그리고 input 태그에는 hidden 기능을 이용해 태그를 숨겨주었음 이러면 우리는 미리보기 이미지를 클릭할 때, 이미지 등록 버튼을 클릭할 때 모두 input type = file 태그를 클릭한 것과 동일한 결과를 볼 수 있다.

전체코드

import { gql, useMutation } from "@apollo/client";
import { ChangeEvent, useRef, 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 ImageRefPage(): JSX.Element {
  const [imageUrl, setImageUrl] = useState("");
  const fileRef = useRef<HTMLInputElement>(null);

  const [uploadFile] = useMutation<
    Pick<IMutation, "uploadFile">,
    IMutationUploadFileArgs
(UPLOAD_FILE);
  const onChangeFile = async (
    event: ChangeEvent<HTMLInputElement>
  ): Promise<void> => {
    const file = event.target.files?.[0]; // 배열로 들어오는 이유: <input type="file" multiple/>일때, 여러 개 업로드 가능하기 때문입니다.
    const result = await uploadFile({ variables: { file } });
    setImageUrl(result.data?.uploadFile.url ?? "");
  };

  const onClickImage = (): void => {
    // 기존 방식 : document.getElementById("파일태그ID")?.click();
    fileRef.current?.click();
  };
  return (
    <>
      <div
        style={{ width: "50px", height: "50px", backgroundColor: "gray" }}
        onClick={onClickImage}

        이미지 선택
      </div>
      <input
        style={{ display: "none" }}
        onChange={onChangeFile}
        type="file"
        ref={fileRef}
      />
      <img src={`https://storage.googleapis.com/${imageUrl}`} />
    </>
  );
}

4. 참고! (label 태그와 htmlFor 사용하기)

두번째 방법으로 label 태그를 이용하는 방법을 알아보자.

Label 태그에는 htmlFor이라는 속성이 있는데, 이 속성에 값을 넣으면 값과 똑같은 id를 찾아 그 태그의 기능과 연결해준다.

<div>
				<label htmlFor="fileTag">이거 눌러도 실행돼요!</label>
				<img style={{ width: '500px' }} id="image" />
				<input id="fileTag" type="file" onChange={readImage}></input>
</div>

Section 19

[목차]

이미지 프로세스 이해 (이미지 서버, 스토리지 포함)

이미지 프로세스를 이해하기 위해서는 storage 사용을 알아야 합니다.

storage 또한 컴퓨터이며 여러 컴퓨터들을 연결시켜 놓은 큰 용량을 담을 수 있는 데이터베이스 입니다.

이미지 업로드

uploadFile 이라는 api가 있을때, 파일을 선택하고 uploadFile을 요청하게 될 경우, backend에서 storage로 파일을 전송하게 됩니다.

이미지 조회

우리가 이미지를 보기 위해서는 주소를 사용해 접근하게 됩니다.

storage에서는 backend로 이미지를 조회할 수 있는 이미지 주소를 넘겨주게 되고, database에 이미지 주소를 저장합니다. (파일을 저장하는 것이 아닌, 주소를 저장!)

frontend에서도 이미지 주소를 통해 이미지를 조회합니다.

(‼️ imgFile에 접근할수있는 주소만 저장, file 없음)

storage는 어디에 있나요?
→ AWS,GCP,AZURE와 같은 클라우드 안에 있습니다.


이미지 업로드해보기

우리가 이미지 업로드에 사용할 api는 uploadFile 입니다.

이미지 업로드는 uploadFile로 받아온 이미지 url을 createBoard에 넣어주시면 됩니다.

url을 받아오기 위해선 아폴로 업로드 관련 라이브러리를 설치해줘야 합니다. 아래 순서를 따라 설치와 세팅을 해주시길 바랍니다.

아폴로 업로드 라이브러리 설치와 세팅

url을 가지고 오기위한 라이브러리로 createUploadLink를 설치해야 합니다.

라이브러리 설치

터미널에 **yarn add apollo-upload-client** 입력해 설치해 설치해주세요

아폴로 업로드의 타입스크립트
아폴로 업로드는 타입스크립트 또한 지원해주는 라이브러리 입니다. 따라서 타입을 설치해주도록 하겠습니다.
설치
**yarn add @types/apollo-upload-client --dev** 를 입력해 설치 해줍니다.

사용을 위한 세팅

  1. app.tsx에서 세팅해주기
**// app.tsx파일**
// import 추가하기
import {createUploadLink} from "apollo-upload-client"

// 세팅 함수 부분
const uplodLink = createUploadLink({
		uri : "백엔드 주소"
	})

const client = new ApolloClient({
		link : ApolloLink.from([uplodLink]),
		cache : new inMemoryCache(),
	})

이렇게 설치와 세팅을 완료하면 사용을 위한 준비는 모두 완료되었습니다.

그럼 본격적으로 이미지를 업로드 해볼까요?

파일 업로드할 수 있도록 화면 그려주기

파일을 선택할 수 있도록 input태그의 type에 file로 지정해 그려주겠습니다.

// 이미지 업로드 api 사용을 위한 쿼리 작성
const UPLOAD_FILE = gql`
  mutation uploadFile($file: Upload!) {
    uploadFile(file: $file) {
      url
    }
  }
`;
export default function ImageUploadPage(): JSX.Element {
  const [imageUrl, setImageUrl] = useState("");
  const [uploadFile] = useMutation<
    Pick<IMutation, "uploadFile">,
    IMutationUploadFileArgs
  >(UPLOAD_FILE);

  // 이미지 업로드 함수
  const onChangeFile = async (
    event: ChangeEvent<HTMLInputElement>
  ): Promise<void> => {
    const file = event.target.files?.[0]; // 배열로 들어오는 이유: <input type="file" multiple/>일때, 여러 개 업로드 가능하기 때문입니다.
    const result = await uploadFile({ variables: { file } });
		console.log(result.data?.uploadFile.url);
    setImageUrl(result.data?.uploadFile.url ?? "");
  };
  return (
    <>
      <input onChange={onChangeFile} type="file" />
      <img src={`https://storage.googleapis.com/${imageUrl}`} />
    </>
  );

이미지를 보내주고, url을 받아오는 과정 을 자세히 보도록 하겠습니다.

  1. onChange를 통해 이미지 파일을 가지고와 file에 넣어줍니다.
  2. file을 variables에 넣어 uploadFile mutation을 날려줍니다.
  3. 이미지가 정해둔 스토리지에 들어가고, 해당 스토리지에서 url을 반환 받아 가지고 옵니다.
  4. 콘솔에 받아온 이미지 URL이 찍힙니다.

‼️ 해당 url의 이미지를 보고싶다면, 저장해둔 스토리지 주소의 뒤에 이미지 url을 적어주시면 됩니다. ‼️ 구글컴퓨터 주소 : http://storage.goolgeapis

(예시)http://storage.goolgeapis.com/이미지url

‼️ 주의 : 여기까지 우리는 이미지를 Storage에 업로드 하는 과정만 진행했습니다. 이미지를 게시글에 등록하기 위해서는 게시글 등록하기 API를 요청해야 합니다.‼️


HTML 기본 FILE 태그 숨기기

아무리 봐도 기본 input 태그는 너무 못생겼습니다.

그래서 input 태그를 숨기고, 좀 더 예쁘게 꾸며주는 방법을 알아보겠습니다.

크게 2가지 방식이 있습니다.

useRef 이용하기

우리가 HTML 태그를 선택할 때는 getElementId를 사용했습니다.

react에서는 HTML 태그에 접근을 도와주는 역할을 useRef가 하고 있습니다.

우선 간단하게 사용 방법을 보도록 하겠습니다.

import { useRef } from 'react';

export default function ImageRefPage(): JSX.Element {
	const fileRef = useRef<HTMLInputElement>(null);

}

****import로 useRef를 가져오고 const fileRef = useRef(); 를 작성했습니다.

이게 가장 처음 Ref를 불러오고 사용하는 기본 설정입니다. useState나 useEffect처럼 react에서 가져와야 사용할 수 있습니다.

이렇게 하고 fileRef를 태그에 연결해주면 해당 태그는 fileRef로 불러서 사용할 수 있습니다.

태그에 넣어주는 것도 한 번 코드로 보곘습니다.

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

이렇게 input 태그에 ref={fileRef}을 작성하면 이제 input 태그를 fileRef을 이용해 사용할 수 있습니다.

그리고 나서, useRef에 있는 기능을 이용해주면 됩니다.

useRef에는 다양한 기능이 있지만, 지금은 하나만 보도록하겠습니다.

const onClickImage = (): void => {
    // 기존 방식: document.getElementById("파일태그ID")?.click();
    fileRef.current?.click();
  };

함수를 하나 만들어주었습니다.

onClick에 넣을 함수인데, Ref에는 current 안에 click이라는 기능이 있습니다.

이름 그대로 current는 fileRef에 들어온 태그를 뜻하고, 그 태그를 click하겠다는 기능입니다.

<button onClick={onClickImage}>이미지 등록 버튼</button>

그리고 새로운 버튼을 하나 만들어서 해당 기능을 넣어주었습니다.

이렇게 하면 button을 클릭했을 때 fileRef.current.click();이 실행될 것이고, 그것은 우리가 useRef에 넣어두었던 input 태그를 클릭한 것과 같은 결과가 나올 것입니다.

보시면 이미지 등록 버튼을 눌렀을 때 input태그를 클릭한 것과 같은 결과가 발생합니다.

여기까지 하면 useRef로 input 태그의 기능을 대신 하는 것은 끝났습니다.

그러면 조금 더 응용을 해볼까요?

return (
		<>
			<div>
				<img **onClick={**onClickImage**}** style={{ width: '500px' }} id="image" />
				<input
					**hidden={true}**
					**ref={**fileRef**}**
					type="file"
					onChange={readImage}
				></input>
				<button onClick={onClickImage}>이미지 등록 버튼</button>
			</div>
		</>
	);

위의 코드를 보시면 미리보기 이미지에도 onClickImage을 넣어주었습니다. onClickImage이 어떤 기능인지는 위에서 설명을 했습니다.

그리고 input 태그에는 hidden 기능을 이용해 태그를 숨겨주었습니다.

이러면 우리는 미리보기 이미지를 클릭할 때, 이미지 등록 버튼을 클릭할 때 모두 input type=file 태그를 클릭한 것과 동일한 결과를 볼 수 있습니다.

전체 코드입니다.

import { gql, useMutation } from "@apollo/client";
import { ChangeEvent, useRef, 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 ImageRefPage(): JSX.Element {
  const [imageUrl, setImageUrl] = useState("");
  const fileRef = useRef<HTMLInputElement>(null);

  const [uploadFile] = useMutation<
    Pick<IMutation, "uploadFile">,
    IMutationUploadFileArgs
  >(UPLOAD_FILE);
  const onChangeFile = async (
    event: ChangeEvent<HTMLInputElement>
  ): Promise<void> => {
    const file = event.target.files?.[0]; // 배열로 들어오는 이유: <input type="file" multiple/>일때, 여러 개 업로드 가능하기 때문입니다.
    const result = await uploadFile({ variables: { file } });
    setImageUrl(result.data?.uploadFile.url ?? "");
  };

  const onClickImage = (): void => {
    // 기존 방식 : document.getElementById("파일태그ID")?.click();
    fileRef.current?.click();
  };
  return (
    <>
      <div
        style={{ width: "50px", height: "50px", backgroundColor: "gray" }}
        onClick={onClickImage}
      >
        이미지 선택
      </div>
      <input
        style={{ display: "none" }}
        onChange={onChangeFile}
        type="file"
        ref={fileRef}
      />
      <img src={`https://storage.googleapis.com/${imageUrl}`} />
    </>
  );
}

4. (참고) label 태그와 htmlFor 사용하기

두번째 방법으로 label 태그를 이용하는 방법을 알아보겠습니다.

Label 태그에는 htmlFor이라는 속성이 있습니다. 이 속성에 값을 넣으면 값과 똑같은 id를 찾아 그 태그의 기능과 연결해줍니다.

<div>
				**<label htmlFor="fileTag">이거 눌러도 실행돼요!</label>**
				<img style={{ width: '500px' }} id="image" />
				<input id="fileTag" type="file" onChange={readImage}></input>
</div>

노란색 부분이 label 태그 입니다. htmlFor="fileTag"input의 id="fileTag" 의 값이 똑같습니다.

5. 이미지 검증

이미지를 업로드할때 필요 이상으로 큰 사이즈의 이미지나, 초고화질 이미지를 보내게되면 저장공간을 많이 차지하기에 비용측면에서 부담이 될 수 있고 이미지 파일을 업로드 해야 하는데 한글파일이나 다른 파일들을 잘못 업로드 하는 경우가 있다. 위와 같은 일들을 방지하기 위해 이미지 업로드를 할 때 크기를 지정하고, 확장자를 검증하는 등 이미지 검증 단계를 거쳐서 업로드를 하는것이 좋다.

이미지 유모와 사이즈 검증하기

이미지를 5MB이하의 사이즈만 넣을 수 있도록 하겠어요!

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

		// 검증 로직
    if (typeof file === "undefined") {
      alert("파일이 없습니다.");
      return;
    }
    if (file.size > 5 * 1024 * 1024) { // 5MB
      alert("파일 용량이 너무 큽니다.(제한: 5MB)");
      return;
    }

		// API 호출 로직
    const result = await uploadFile({ variables: { file } });
    setImageUrl(result.data?.uploadFile.url ?? "");
  };

이미지 확장자 검증하기

이미지 확장자가 png, jpeg가 아니면 업로드 할 수 없도록 막도록 하겠스무니당.

방법 1. 사진 선택 시 호출되는 함수에서 확장자 검증하기

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

		~~~	// 기타 검증 로직 생략

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

		~~~ // API 호출 로직 생략

};

if문의 모습이 익숙한데...혹시 early-exit패턴?
→ 이처럼 early-exit패턴을 적용하시면 훨씬 깔끔하고 가독성있는 코드를 작성하실 수 있습니다.

방법 2. 지정하지 않은 확장자의 파일은 선택 자체가 불가하도록 막는 방법

<input
style={{ display: "none" }}
onChange={onChangeFile}
type="file"
ref={fileRef}
accept="image/jpeg,image/png" // 띄어쓰기 없이 콤마(,)를 기준으로 작성합니다.
// accept를 추가하면 지정되지 않은 확장자는 선택 자체가 불가합니다.
/>

기존 버튼(type="file") 안보이게 숨기기 (수업 실습)

위까지 바인딩 했으면 기존에 파일선택 버튼과, 새로만들어준 예쁜 버튼 두개가 있을거고, 두개 모두 onClickImage를 적용시켜 주었다면, 두개가


중괄호 두개인 이유 마이스타일 한번에 가져온거


리액트 사용 전

태그 담는 스테이트는 useRef 인데 얘는 qqq에 어케 담냐 위처럼 담을수없다

그래서 이렇게 다믐

1q번 종료해야하는데 지금 2번 종료됨 ㅎ나마나

💡 Tip !!



컴퓨터가 말하는 숫자.. 3은 0011이다.

이게 1024임. 저걸 10번 해서 1024

꺼짐은 0,, 켜짐은 1

비트가 8개 가 있으면 1바이트다.

색을 만드려면 1픽셀(색깔1칸) 만들기 위해 3바이트가 필요하다.

사이즈 보는 방법

MB(메가바이트) KB(키로바이트) B(바이트)

1024B = 1KB

1024KB = 1MB

이미지 검증과정 컴포넌트 분리

이런 검증과정은 다른 컴포넌트에서도 사용해야 할 수 있다! 따로 빼두면 나중에 import해서 사용가능!! 나눠봅시다.

// src/commons/libraries/validationFile.ts

export const checkValidationFile = (file?: File): boolean => {
  if (typeof file === "undefined") {
    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문에 return 을 넣어주어 함수를 종료 시켜주었지만, 파일을 분리하게 되면 검증함수를 종료할 뿐 이미지 업로드 함수를 종료하지 못함.

따라서 검증함수를 따로 분리했을 때는 true와 false를 return 하여 결과를 변수에 담아 해당 변수의 Boolean값에 따라 return으로 업로드를 막거나 업로드를 진행 해주면 된다.

// 이미지 검증을 실행 할 컴포넌트

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

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

    const result = await uploadFile({ variables: { file } });
    setImageUrl(result.data?.uploadFile.url ?? "");
  };

uuid4() -> yarn add uuid


🤷🏻‍♀️ 궁금한 것


❗️ 알게된 것


✨ 느낀 것

profile
Slow and steady wins the race.

0개의 댓글