FormData로 프로필 이미지 미리 보고 수정하기

Wynter24·2023년 10월 25일
2

이미지의 미리보기와 수정에 FormData를 사용하는 이유와 관련된 전체적인 흐름과 로직에 대해 알아보자!!

1. 이미지 미리보기와 수정의 기본 과정

  1. 사용자가 이미지 선택: 웹 페이지에 <input type="file"> 요소를 사용하여 사용자로부터 이미지나 파일을 선택받는다.

  2. 미리보기 생성: 선택된 이미지를 브라우저에서 바로 보여주기 위해 FileReader API를 사용하여 이미지를 읽어 들인다. 읽어들인 이미지 데이터는 Blob 또는 Data URL 형식으로 생성되며, 이를 <img> 태그의 src 속성에 할당하여 미리보기를 제공한다.

  3. 이미지 수정 및 업로드 준비: 사용자가 이미지를 수정하거나 다른 이미지를 선택하면, 해당 이미지 데이터는 FormData 객체에 포함되어 서버로 전송될 준비를 한다.

  4. 서버로 이미지 전송: 수정된 이미지를 서버에 저장하기 위해 FormData 객체를 사용하여 서버에 POST 또는 PUT 요청을 한다.

2. FormData의 사용 이유

  • 멀티파트 폼 데이터 전송: 이미지와 같은 바이너리 파일은 HTTP 요청 본문에 직접 포함시키기 어렵다. 이러한 경우, 멀티파트 형식(multipart/form-data)을 사용하여 파일과 텍스트 데이터를 함께 전송할 수 있다. FormData API는 이 멀티파트 형식을 자동으로 관리해주므로, 파일과 텍스트 데이터를 함께 쉽게 전송할 수 있다.

  • 동적 폼 데이터 생성: JavaScript를 사용하여 동적으로 폼 데이터를 생성하거나 수정할 수 있다. FormData 객체는 키-값 쌍 형태로 데이터를 추가, 삭제, 수정하는 메서드를 제공한다.

  • 브라우저 호환성: 대부분의 모던 브라우저에서 FormData API를 지원한다.

3. 전체적인 로직

  1. File Input 변경 감지: 사용자가 파일 입력 요소를 통해 이미지를 선택하면 onChange 이벤트가 발생한다.

  2. 이미지 미리보기: 선택된 이미지는 FileReader API를 사용하여 읽히고, 미리보기 이미지로 표시된다.

  3. FormData 생성: 이미지 미리보기 후, 해당 이미지 데이터는 FormData 객체에 추가된다. 이 객체는 나중에 서버로 이미지를 전송하기 위한 준비로 사용된다.

  4. 이미지 전송: 사용자가 "저장" 버튼을 클릭하면, FormData 객체가 서버로 전송된다. 여기서 Content-Type: multipart/form-data 헤더를 통해 이미지와 함께 다른 폼 데이터도 함께 전송될 수 있다.

이러한 흐름을 따르면, 웹 애플리케이션은 사용자에게 이미지 미리보기 기능을 제공하면서도, 실제 이미지 데이터는 서버로 안전하게 전송할 수 있다.


이미지 미리보기

createObjectURL와 FileReader 차이점

window.URL.createObjectURL(file)

이 함수는 선택된 파일(이 경우 이미지)에 대한 Blob URL을 생성한다. Blob URL은 파일의 내용을 직접 참조하는 URL이며, 해당 URL을 <img> 태그의 src 속성에 설정하면 브라우저가 이미지를 렌더링한다.
장점: 빠르고 효율적이다. 큰 파일에 대해서도 상대적으로 빠르게 작동한다.
단점: Blob URL은 메모리를 사용하므로 더 이상 필요하지 않을 때 window.URL.revokeObjectURL(url)을 호출하여 해제하는 것이 좋다.

if (file) {
        const image = window.URL.createObjectURL(file);
        setImgFile(image);
        // 새로운 FormData 생성
        const newFormData = new FormData();
        newFormData.append('image', file);

        // FormData를 상태로 설정
        setFormData(newFormData);
      }

FileReader.readAsDataURL(file)

FileReader 객체를 사용하여 선택된 파일을 Data URL로 읽는다. Data URL은 파일의 내용을 Base64로 인코딩한 문자열 형태의 URL이다. 이 URL을 <img> 태그의 src 속성에 설정하면 브라우저가 이미지를 렌더링한다.
장점: Base64로 인코딩된 데이터를 포함하므로 별도의 해제 과정이 필요하지 않다.
단점: Base64 인코딩은 원본 파일 크기보다 약 33% 더 크기 때문에 큰 이미지의 경우 메모리 사용량이 더 클 수 있다. 또한, 인코딩 과정에서 약간의 시간이 걸릴 수 있다.

if (file) {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onloadend = () => {
          setImgFile(reader.result as string);

          // 새로운 FormData 생성
          const newFormData = new FormData();
          newFormData.append('image', file);

          // FormData를 상태로 설정
          setFormData(newFormData);
        };
      }

코드로 알아보기

이미지를 수정하기 위해 axios를 사용하여 PUT 요청을 보내려면 FormData 객체를 사용해야 한다. FormData 객체는 multipart/form-data 인코딩을 사용하여 요청 본문에 파일 데이터를 포함시킬 수 있다.

다음은 axios를 사용하여 이미지를 수정하기 위한 PUT 요청을 하는 방법에 대한 기본적인 단계이다.

  1. FormData 객체 생성:

    const formData = new FormData();
  2. 이미지 파일 추가: 파일 입력 필드나 드래그 앤 드롭 방식 등을 통해 사용자가 선택한 이미지 파일을 FormData 객체에 추가한다.

    formData.append('profileImage', selectedFile); // 'profileImage'는 서버에서 예상하는 필드 이름입니다.
  3. axios를 사용하여 PUT 요청 보내기:

    import axios from 'axios';
    
    axios.put('https://your-api-endpoint.com/profile', formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
    .then(response => {
      console.log('Image successfully updated', response.data);
    })
    .catch(error => {
      console.error('Error uploading image', error);
    });

'Content-Type': 'multipart/form-data'는 HTTP 헤더의 일부로 사용되며, 클라이언트가 서버로 보내는 HTTP 요청의 본문 데이터 유형을 설명한다.

multipart/form-data는 주로 HTML 폼에서 파일 업로드를 할 때 사용하는 MIME 타입이다. 여러 파트로 나누어진 데이터를 하나의 요청에 포함시키기 위해 사용되며, 각 파트는 자체적인 헤더 세트를 가진다.

왜 사용하는가?

  • 파일 업로드: 웹 어플리케이션에서 파일을 업로드 할 때, 파일의 바이너리 데이터와 폼의 다른 필드(텍스트 등)를 함께 전송할 수 있다.

  • 복합 데이터 전송: 하나의 요청에서 텍스트와 파일, 그 외의 데이터를 함께 전송할 수 있다.
    웹에서는 특히 이미지나 동영상 등의 미디어 파일, 그외의 바이너리 데이터를 전송할 때 이 방식이 사용된다.

axios나 다른 HTTP 클라이언트 라이브러리를 사용하여 프로그래밍 방식으로 요청을 보낼 때, 해당 요청이 파일을 포함한다면 Content-Type 헤더를 multipart/form-data로 설정해야 한다.


코드

이미지 미리보기

const saveImgFile = () => {
    if (fileInputRef.current) {
      const file = fileInputRef.current.files?.[0];
      if (file) {
        if (file.size > 5 * 1024 * 1024) {
          toast.error('5MB 이하만 업로드 가능해요 🙇‍♀️');
          return;
        }
        if (
          !['image/jpeg', 'image/jpg', 'image/png', 'image/bmp'].includes(
            file.type,
          )
        ) {
          toast.error('지원하지 않는 파일 형식입니다!');
          return;
        }
      } 
      if (file) {
        const image = window.URL.createObjectURL(file);
        setImgFile(image);
        // 새로운 FormData 생성
        const newFormData = new FormData();
        newFormData.append('newImage', file);

        // FormData를 상태로 설정
        setFormData(newFormData);
      }
    }
    console.log(formData);
  };

api 연결

const putProfile = async () => {
    try {
      const response = await axios.put(`${API_BASE_URL}/api/member/update/profile`, formData, {
        headers: { 
          'Content-Type': 'multipart/form-data', 
          Authorization: `${localStorage.getItem('Authorization')}` 
        },
      });
      console.log(response);
    } catch (error) {
      console.log(error);
    }
  }
profile
내가 다시 보려고 쓰는 개발.log

0개의 댓글