문제정의
- 프로젝트에서 상품을 최초 등록할 시에는 상품의 이미지 파일의 처리를 위해 상품의 정보(제목, 가격, 이미지 등)를 formData 객체로 만들어 전송했고, 해당 상품의 조회(상세페이지), 수정(작성페이지)에서는 DB에서 상품의 이미지 url을 받아 화면에 표시했다.
- 문제가 발생한 부분은 상품의 수정(작성페이지)에서 상품의 정보를 수정한 후 수정 완료를 했을 때, 프론트에서 가지고 있는 정보는 상품 이미지의 url 뿐이었기 때문에 비동기 통신 오류가 발생했다.
- 오류의 이유는, 상품의 작성 및 수정은 상품의 정보를 모두 formData로 만들어서 전송을 해야했고, url만 가지고 있는 상품의 이미지를 다시 formData 객체로 만드는 작업이 필요했다.
- 오류 해결을 위해 백엔드 동료와 회의를 진행한 결과, 두 가지 방법이 있다고 판단했다.
시도한 방법 및 해결방안
1. 처음 고민했던 방법은 해당 상품의 조회(상세페이지), 수정(작성페이지)에서 상품의 이미지를 화면에 표시할 때, DB에서 이미지의 url 뿐만 아니라, 이미지 file도 함께 받아서 상품 정보 수정시에 해당 file을 이용해 formData 객체를 만들기다.
- 이미 등록된 이미지의 경우, 프론트에서는 이미지 url만으로는 formData 객체를 만들 수 없었기 때문에 이미지 file을 함께 받는 방법을 고려했다.
- 하지만, 백엔드 동료의 의견은 이미지 파일의 크기가 큰 경우에는 최대 이미지 개수가 10개이지만, 비동기 통신 속도에 영향을 미칠 수도 있다는 의견이 있었다.
2. 다음으로 고민했던 방법은 상품 정보 수정시에 상품 이미지 url으로 S3에 접근하여 해당 이미지 file을 받고, 이미지 file로 formData를 만드는 것이었다.⭕️
- 이전에는 비동기 통신은 항상 백엔드 서버를 통해 DB에서 데이터를 받아왔기 때문에, S3에 접근하는 방법에 대해서 감이 잡히지 않았지만 구글링을 통해서 S3에 접근하는 방법을 찾을 수 있었고, 우리 팀은 이 방법을 사용하기로 했다.
- 기능 구현 방법은 다음과 같다.
// 이미지 URL을 통해 S3에 접근하여 이미지 파일을 받아오고
// blob() 메서드를 통해 새로운 blob 객체를 반환
export const fetchImageAsData = async (imageUrl) => {
const imageData = await fetch(imageUrl);
if (!imageData.ok) {
throw new Error('이미지를 불러오는데 실패했습니다.');
}
const blob = await imageData.blob();
return blob;
};
// 상품 수정 등록 API
// blob 객체를 이용해서 이미지 file 객체를 만든다.
export const uploadEditItems = async (
itemIdx: number,
{ title, description, images, price, categoryIdx, locationIdx, status }
) => {
try {
const convertedImages = await Promise.all(
images.map(async (image) => {
if (!image.file) {
const blob = await fetchImageAsData(image.fileString);
const urlParts = image.fileString.split('/');
const fileName = urlParts[urlParts.length - 1];
const fileReader = new FileReader();
fileReader.readAsDataURL(blob);
const fileString = await new Promise((resolve) => {
fileReader.onloadend = () => {
resolve(fileReader.result);
};
});
// 새로운 file 객체를 만들기
const file = {
file: new File([blob], fileName, { type: blob.type }),
fileString: fileString,
name: fileName,
size: blob.size,
};
return file;
}
return image;
})
);
// 데이터의 key 값 관리 함수
const body = convertDataToBody(
title,
description,
convertedImages,
price,
categoryIdx as number,
locationIdx,
status
);
const result = await editItemsAPI(itemIdx, body);
return result as Response<null>;
} catch (error) {
console.error(error);
throw error;
}
};
// 상품의 수정된 정보를 formData 객체로 만들고 customFetch를 통해 비동기 통신
export const editItemsAPI = async (itemIdx: number, body: ItemReqBody) => {
...
const formData = new FormData();
formData.append('name', convertedBody.name);
formData.append('description', convertedBody.description);
formData.append('price', convertedBody.price);
formData.append('locationIdx', convertedBody.locationIdx);
formData.append('categoryIdx', convertedBody.categoryIdx);
convertedBody.status ? formData.append('status', convertedBody.status) : null;
convertedBody.images?.forEach((image) => {
formData.append(`image`, image);
});
...
};
배운 점
- 프로젝트를 진행하면서, 어느 순간부터 아는 것들을 활용하는데 집중했었는데 S3에 접근과 같이 처음 접해보고 적용하는 것도 관련 내용을 찾아보고 학습한다면 해결할 수 있다는 것을 다시 한번 더 깨달았다.💪
'프로젝트 > 세컨핸드' 카테고리의 다른 글
[기능 구현] 동적 portal 만들기 (0) | 2024.01.22 |
---|---|
[기능 구현] 커스텀 훅(Custom Hook)을 사용하여 비동기 로딩 및 에러 상태 처리 (0) | 2024.01.22 |
[기능 구현] OAuth를 통한 로그인 기능 구현 (0) | 2024.01.22 |
[Trouble Shooting] 상품 수정 등록 후 뒤로가기 경로 문제 (0) | 2023.11.23 |
[Trouble Shooting] 지역 설정 기능 구현 과정에서 겪은 문제 (0) | 2023.09.21 |