이미지와 함께 백이랑 통신하기

Yunseok Choi·2024년 4월 9일

Palette 프로젝트

목록 보기
2/10

1️⃣ intro

이미지를 서버에 전달/받음, 즉 통신을 하기 위해서는 먼저 이미지의 상태를 저장해야 한다.

input file타입을 통해 해도 되거나 드랍같은 기능을 추가하고 싶으면 라이브버를 통해 구현해도 된다. (필자는 react-dropzone을 사용하였다.)

react-dropzone 정리 URL : https://velog.io/@hiyunseok347/react-dropzone-%EC%A0%95%EB%A6%AC

2️⃣ 이미지 정보 상태 저장하기

const formData = new FormData();

const onDropThumbnail = useCallback((acceptedFiles: File[]) => {
  const file = acceptedFiles[0];
//백과 통신하기 위한 동작
  formData.append("file", file);
  setFormDataFile(formData);//전역상태에 담기위한 함수
//이미지 미리보여주기를 위한 동작
  //createObjectURL는 임시로 URL을 저장할수 있는 메서드
  const fileURL = URL.createObjectURL(file);
  setThumbnailPreview({ url: fileURL });
}, []);

formData를 사용하기 전에, 이 위 코드에서 추출한 fileURL을 처음에는 그냥 사용면 되겠지 했는데 임시로 랜덤한 주소에 저장하는 값이다 보니, 새로고침하면 그 주소값은 유효해지지 않는다. 그래서 form데이터로 파일을 전송해 줘야 한다.
위 코드처럼 FormData를 선언해주고 생성한 함수를 통해 파일의 value를 받아서 formDataappend해준다.(file타입 input이면 onChange옵션에 이 함수를 넣고 e.target.value를 넘겨주면 된다.)
그리고 필자는 이미지와 텍스트 정보를 함께 백으로 넘겨주기 때문에 setFormDataFile이란 전역상태함수로 formData를 담았다.

(이미지 미리보여주기를 원하면 함수 아래쪽처럼 url을 생성해서 img/Image태그 src에 넣으면 된다.)

3️⃣ 텍스트 정보도 함께 저장

이제 텍스트 차례이다. 원래는 그냥 객체를 생성해서 백과 통일된 키값에 상태(텍스트정보)를 넘겨주기만 하면 됐는데 이미지까지 추가하다보니 같은 formData로 넣어서 함께 보내야 했다. 구글링을 통해 이 방법을 알아냈고 이를 사용했다.

텍스트 데이터를 FormData에 추가하는 이유 : 파일과 관련된 메타데이터 또는 기타 추가 정보를 함께 전송하기 위함. (http body에 결국 하나의 뭉텅이 데이터를 실어야 함) 밑의 코드에서 body 객체를 JSON 문자열로 변환한 후에 Blob으로 만들어 FormData에 추가해준다. 이렇게 함으로써 JSON 형식의 텍스트 데이터를 함께 전송할 수 있다.

const body: ResumePostDto = {
  name: personalInfo.name,
//...기타 정보들
};

const json = JSON.stringify(body);
const blob = new Blob([json], { type: "application/json" });
formDataFile?.append("requestDto", blob);

이렇게 해서 json형식으로 처리된 blob데이터를 formDatafile(필자가 선언한 전역 formData)에 append해서 이걸 통신할 때 밑 코드처럼 body에 넣으면 된다.

const response = await userAxiosWithAuth2.post(
  `${process.env.NEXT_PUBLIC_BASE_URL}/resume`,
  formDataFile
);
//userAxiosWithAuth2 : 유저 토큰을 같이 보내주는 커스텀 axios 함수

4️⃣ http 헤더 정의

통신을 하기 위해 http헤더에 Content-type을 정의해 줘야 한다. 원래는 텍스트만 넘겨줘서 application/json으로만 했는데 텍스트와 이미지가 함께 있어서 multipart/form-data로 변경하고 데이터를 넘겨주었다.

const userAxiosWithAuth2 = axios.create({
  baseURL: process.env.NEXT_PUBLIC_BASE_URL,
  headers: { "Content-type": "multipart/form-data" },
});

⚠️ 문제 서론

이렇게 사진도 넣고 텍스트도 넣고 백에 보내서 생성도 성공하고 다 잘 되는 듯 굴러갔다. 근데 문제가 발생한다.

⚠️ 문제 상황

근데 만약에 사진을 넣고 싶지 않고 그냥 텍스트만 보내고 싶다면?

에러가 난다. 에러가 났다. 이걸 어떻게 해결해야되지? 하고 멘탈이 나갔다. 구글링을 해보고 'http 헤더에 관한 문제다, 원래는 브라우저가 데이터를 인식해서 자동으로 header를 지정해준다, 그래서 지정 자체를 안해줘야 한다.'라는 글들을 보고 지정 자체도 안 해보고 다른 타입들도 넣어보고 여러 시도를 해봤지만 모두 에러가 나면서 실패하였다.

⚠️ 문제 해결

문제 이유는 결국 찾지 못하고 머리를 써서 문제를 해결하기로 했다. formData의 이미지의 유무를 판가름해서 http header 타입을 바꿔서 통신을 하기로 한 것이다.

if (formDataFile?.get("file") === undefined) {
  response = await userAxiosWithAuth.post(
    `${process.env.NEXT_PUBLIC_BASE_URL}/resume`,
    body
  );
} else {
  response = await userAxiosWithAuth2.post(
    `${process.env.NEXT_PUBLIC_BASE_URL}/resume`,
    formDataFile
  );
}

위의 코드처럼 통신을 할 때 formdata의 file이란 값이 있는지 확인하고 없으면 application/json, 있으면 multipart/form-data를 넣어서 통신을 하기로 정했고, 이 방법으로 문제를 해결하였다.

⚠️ 마이너 문제 상황

이미지 삭제 과정에서 나는 분명 form데이터의 이미지에 빈값("")을 주었는데 삭제가 안된다. 코드를 계속 보고 여러 시도 끝에 문제를 해결하였다.

⚠️ 문제 이유

const handleDeleteThumbnail = (e: MouseEvent<HTMLButtonElement>) => {
  e.stopPropagation();
  formData?.append("file", ""); 
  setThumbnailPreview({ url: "" }); //미리보기 이미지 src 삭제
};

위 코드를 보면 알겠지만 이상한게 보일 것이다. 선언한 formData는 값을 업데이트하지만 통신에서 쓰는 body값에 쓰는 전역상태의 formData에 넣지 않지 않았기에, 사실 상 그냥 아무것도 안한 것이다.

⚠️ 문제 해결

const handleDeleteThumbnail = (e: MouseEvent<HTMLButtonElement>) => {
  e.stopPropagation();
  formData?.append("file", "");
  setFormDataFile(formData);
  setThumbnailPreview({ url: "" });
};

그래서 setFromDataFile함수에 file 키에 빈값을 넣어준 formData상태를 넣어서 통신에 넣는 전역상태 정보를 업데이트시켜주었고, 문제를 해결하였다.

profile
이용자와 서비스를 하나로 만드는 개발자, Hermann입니다.

0개의 댓글