[React+Typscript] 파일업로드 기능 구현(With. Springboot)

anonymous-planet·2022년 6월 15일
7

React+Typescript

목록 보기
4/6

저의 포스팅에서는 자세한 지식보다는 사용법위주로 작성되며, 개인적인 내용을 포함하고 있습니다!

이번 포스팅은 파일 업로드/다운로드 중 파일 업로드를 할예정이다.

본캐가 백엔드 개발자 이기때문에 이번에는 백엔드도 만들어 API요청을 보내 확인 해볼 예정이다.

1. 흐름

업로드 할 파일선택 > 업로드 버튼 클릭 > 파일 validation > 파일 업로드 API요청(Axios이용) > 백엔드로 파일 넘어온것 확인

2. 구현

2.1. 구현할 기능

파일 valiadation

  • 확장자 체크 (가능한 확장자 png, jpg, jpeg)
  • 용량제한 5MB

파일 업로드 기능

2.2. 소스

2.2.1. FileUploadPage.tsx

  • 주석 내용 참고, 주석 내용이 많이 좀 지저분한데 양해바랍니다...............
import { axiosDefaultInstance } from 'apis';
import React, { useState } from 'react';

// 허용가능한 확장자 목록!
const ALLOW_FILE_EXTENSION = "jpg,jpeg,png";
const FILE_SIZE_MAX_LIMIT = 5 * 1024 * 1024;  // 5MB

export default function FileUploadPage() {

  // 업로드할 파일들을 담을 State!
  const [file, setFile] = useState<File>();

  /**
   * 파일 선택 onChangeHandler
   * 해당 method에서는 업로드할 파일에대해서 validaion을 하고
   * file state에 값을 할당한다
   * @param e 
   * @returns 
   */
  const fileUploadValidHandler = (e:React.ChangeEvent<HTMLInputElement>) => {
    const target = e.currentTarget;
    const files = (target.files as FileList)[0];

    if(files === undefined) {
      return ;
    }

    // 파일 확장자 체크
    if(!fileExtensionValid(files)) {
      target.value = '';
      alert(`업로드 가능한 확장자가 아닙니다. [가능한 확장자 : ${ALLOW_FILE_EXTENSION}]`)
      return;
    }

    // 파일 용량 체크
    if(files.size > FILE_SIZE_MAX_LIMIT) {
      target.value = '';
      alert('업로드 가능한 최대 용량은 5MB입니다. ')
      return;
    }

    // validation을 정상적으로 통과한 File
    setFile(files);
  }


  /**
   * 파일업로드 버튼 클릭 Handler
   * 해당 method에서는 api를 호출해 file을 백엔드로 전송하고, 응답값에 대한 처리를 해준다.
   */
  const fileUploadHandler = async () => {
    // narrowing(?)
    if(file !== undefined) {
      try{
        // !!중요1. formData활용!!
        const formData = new FormData();
        formData.append('file', file);

        // Axios를 이용해서 Back-End로 파일 업로드 요청!
        // !!중요2. header에 content-type에 multipart/form-data를 설정!!
        const axiosResponse = await axiosDefaultInstance.post<ApiResponse<FileUploadResponse>>("/files", formData, {"headers" : {"content-type" : "multipart/form-data"}})

        // HttpStatus가 200번호 구역이 아니거나
        // 서버에서 응답 코드로 0(성공)을 주지 않았을 경우
        if(axiosResponse.status < 200 || axiosResponse.status >= 300 || axiosResponse.data.resultCode !== 0){
          // Error를 발생시켜 Catch문을 타게 만들어주는데, 서버에 응답받은 메시지를 넣어준다!
          // 서버에서 응답 메시지를 받지 못했을경우 기본 메시지 설정또한 함께 해준다
          throw Error(axiosResponse.data.message || "문제가 발생했어요!");
        }
        // 파일 업로드 성공!
        alert('파일 업로드 성공!')
        console.log(axiosResponse.data.data)
      } catch(e) {
        console.error(e);
        alert((e as {message : string}).message);
      }
    }
  }

  return (
    <>
      <h1>파일 업로드 페이지</h1>
      <input type="file" onChange={fileUploadValidHandler}/>
      <br/><br/><br/>
      <button onClick={fileUploadHandler}>파일 업로드 하기</button>
      <hr/>
    </>
  );
}

/**
 * 파일 확장자를 검사해주는 함수이다.
 * @param param
 * @returns true: 가능 확장자, false : 불가능 확장자 
 */
const fileExtensionValid = ({name} : {name : string}):boolean =>{
  // 파일 확장자
  const extension = removeFileName(name);

  /**
   * 허용가능한 확장자가 있는지 확인하는 부분은 indexOf를 사용해도 괜찮고, 
   * 새롭게 나온 includes를 사용해도 괜찮고, 그밖의 다른 방법을 사용해도 좋다.
   * 성능과 취향의 따라 사용하면 될것같다.
   * 
   * indexOf의 경우
   * 허용가능한 확장자가 있을경우 
   * ALLOW_FILE_EXTENSION 상수의 해당 확장자 첫 index 위치값을 반환
   */
  if(!(ALLOW_FILE_EXTENSION.indexOf(extension) > -1) || extension === '') {
    // 해당 if문이 수행되는 조건은
    // 1. 허용하지 않은 확장자일경우
    // 2. 확장자가 없는경우이다.
    return false;
  }
  return true;
}

/**
 * 해당 함수의 기능은 .을 제거한 순수 파일 확장자를 return해준다.
 * @param originalFileName 업로드할 파일명
 * @returns .을 제거한 순수 파일 확장자(png, jpg 등)
 */
const removeFileName = (originalFileName:string):string => {
  // 마지막 .의 위치를 구한다
  // 마지막 .의 위치다음이 파일 확장자를 의미한다
  const lastIndex = originalFileName.lastIndexOf(".");

  // 파일 이름에서 .이 존재하지 않는 경우이다.
  // 이경우 파일 확장자가 존재하지 않는경우(?)를 의미한다.
  if(lastIndex < 0) {
    return "";
  }

  // substring을 함수를 이용해 확장자만 잘라준다
  // lastIndex의 값은 마지막 .의 위치이기 때문에 해당 위치 다음부터 끝까지 문자열을 잘라준다.
  // 문자열을 자른 후 소문자로 변경시켜 확장자 값을 반환 해준다.
  return originalFileName.substring(lastIndex+1).toLowerCase();
}

2.3. 결과 화면

  • 제가 CSS를 몰라 화면이 단순합니다.............

3. 후기

  • Typescript Type에 대한 이해도 부족
    • onChange Handler에서 event타입은 어떻게 되는지
      upload할 file에 타입은 어떻게 되는지
      upload할 file을 state에 할당 해주고 싶은데 state타입은 어떻게 해줘야하는지 등등 이런 부분에 대해서 이해도가 많이 부족
  • Component화 능력 부족
    • 이번 file업로드 부분은 component를 분리시켜 복잡하더라도 좀더 깔끔하게 해보고 싶었는데, React에 대한 이해도가 부족

4. 정보

혹시 직접 구동 Back-End요청까지 해보시고 싶은 분이 계실수도 있을 것 같아서 Back-End까지 같이 첨부합니다.
(소스는 곧 올리겠습니다.)

Front-End [GitHub]

Back-End[GitHub]

profile
취미로 Front-End를 즐기는 Back-End개발자 입니다.

0개의 댓글