개요
포토 부스 애플리케이션 프로젝트에서 주요 기능 구현을 맡게 되었다. 그 중 하나는 사용자가 찍은 사진과, 찍는 과정이 녹화된 영상을 서버에 전송해야 했다. 네컷사진을 떠올려보면 될 것 같다.
이 과정에서 FormData를 활용해 멀티파트 형식으로 데이터를 전송하는 방법을 적용했다.
FormData는 HTML 폼 데이터를 쉽게 구성하고 전송할 수 있게 해주는 웹 API이다. 파일 업로드와 같은 바이너리 데이터 전송에 보통 쓰인다고 한다.
본 프로젝트에서는 한 장의 합성 이미지와 네 개의 개별 영상 파일을 동시에 전송해야 했기 때문에, FormData를 사용하는 것이 좋을 것이라 판단되었다.
TypeScript를 사용한 것도 큰 도움이 되었다. 아직 TypeScript가 완전히 익숙해지지는 않았지만, API 요청과 응답 과정에서 타입이 지켜지지 않은 상태에서 버그가 터지면 잡기 힘들었을 것이다. TypeScript 덕분에 컴파일 시점에 미리 방지할 수 있었다.
특히 API를 보내면서 선행 작업들이 매우 복잡했기 때문에 장점이 더욱 두드러졌다.
FormData란
FormData는 웹 개발에서 자주 사용되는 API다.
HTML 폼 데이터를 구성하고 전송하는 데 사용되며, 주로 AJAX 요청을 통해 서버로 데이터를 보낼 때 활용된다.
FormData 객체는 키-값 쌍의 집합으로, HTML <form>
요소의 데이터를 TypeScript 코드로 직접 생성하고 수정할 수 있게 해준다.
예를 들어, FormData.append('key', value)
메소드를 사용해 새로운 데이터를 추가하거나, FormData.get('key')
메소드로 기존 데이터를 읽을 수 있다.
FormData는 몇 가지 특징을 가지고 있다.
- 문자열, 파일, Blob 객체 등 다양한 타입의 데이터를 포함할 수 있어 여러 상황에서 사용할 수 있음
- 파일 업로드에 필요한 형식인 멀티파트/폼-데이터(multipart/form-data) 인코딩을 자동으로 사용
- 데이터를 동적으로 추가, 삭제, 수정할 수 있음
FormData.append()
,FormData.delete()
, …
프로젝트에서의 FormData 사용
이번 개발에서 FormData를 사용했다. 프로젝트의 요구사항 중 하나는 사용자가 찍은 한 장의 합성 이미지와 네 개의 개별 영상 파일을 하나의 요청으로 서버에 전송하는 것이었다. 이를 위해 FormData를 선택했다.
FormData 사용으로 여러 개의 파일을 하나의 요청으로 전송할 수 있었고, 파일 이름, MIME 타입 등의 메타데이터를 자동으로 포함시켜 전송할 수 있었다.
axios와 함께 사용하여 파일 업로드 진행 상황을 모니터링할 수 있었다.
// ...
formData,
{
headers,
onUploadProgress: (progressEvent) => {
const percentCompleted = progressEvent.total
? Math.round((progressEvent.loaded * 100) / progressEvent.total)
: 0;
console.log(`Upload Progress: ${percentCompleted}%`);
},
responseType: "arraybuffer",
}
// ...
FormData는 이미지나 비디오와 같은 대용량 바이너리 데이터를 다룰 때 좋은 도구다.
JSON 형식으로는 대용량 바이너리 데이터를 전송하기 어려운 반면, FormData를 사용하면 비교적 간단하게 처리할 수 있다.
1. 파일 업로드 함수 구현
uploadPhoto
함수를 구현했다. 이 함수는 1개의 이미지 파일과 4개의 비디오 파일을 받아 서버로 전송한다.
export const uploadPhoto = async (
imageFile: File,
videoFile1: File,
videoFile2: File,
videoFile3: File,
videoFile4: File
): Promise<UploadPhotoResponse> => {
const formData = new FormData();
formData.append("fourCutImage", imageFile);
formData.append("videoLU", videoFile1);
formData.append("videoRU", videoFile2);
formData.append("videoRD", videoFile3);
formData.append("videoLD", videoFile4);
const accessToken = getAccessToken();
if (!accessToken) {
throw new Error("Access token이 없습니다.");
}
const headers = {
"Content-Type": "multipart/form-data",
auth: accessToken,
};
try {
const response = await apiService.post<UploadPhotoResponse>(
// ...
);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
// ...
}
throw error;
}
}
uploadPhoto
함수는 async
키워드로 정의된 비동기 함수이며, 내부에 await
키워드를 사용하고 있다. 이로 인해 함수의 동작은 다음과 같이 이루어진다.
이러한 방식으로, uploadPhoto
함수는 비동기적으로 동작하면서도 동기적인 코드처럼 읽히는 장점을 가진다.
함수 전체가 완료되기 전까지는 호출한 곳으로 제어권이 완전히 반환되지 않지만, await
지점에서 일시적으로 다른 작업을 수행할 수 있게 된다.
파일 업로드 진행 상황은 onUploadProgress
콜백을 통해 실시간으로 모니터링할 수 있다. 업로드가 진행되는 동안 주기적으로 호출된다.
const response = await apiService.post<UploadPhotoResponse>(
API_CONFIG.ENDPOINTS.UPLOAD_PHOTO,
formData,
{
headers,
onUploadProgress: (progressEvent) => {
const percentCompleted = progressEvent.total
? Math.round((progressEvent.loaded * 100) / progressEvent.total)
: 0;
console.log(`Upload Progress: ${percentCompleted}%`);
},
responseType: "arraybuffer",
}
);
References
https://developer.mozilla.org/en-US/docs/Web/API/FormData
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects
'Web, Front-end' 카테고리의 다른 글
[React] useEffect의 내부적인 동작 (3) | 2024.12.13 |
---|---|
[Javascript] 브라우저 팝업 차단으로 인한 문제와 해결책 (0) | 2024.12.01 |
[Javascript] 이벤트 전파 (1) | 2024.08.27 |
[Javascript] 이벤트 핸들러 등록 (0) | 2024.08.27 |
[Javascript] const 키워드 (0) | 2024.06.05 |
컴퓨터 전공 관련, 프론트엔드 개발 지식들을 공유합니다. React, Javascript를 다룰 줄 알며 요즘에는 Typescript에도 관심이 생겨 공부하고 있습니다. 서로 소통하면서 프로젝트 하는 것을 즐기며 많은 대외활동으로 개발 능력과 소프트 스킬을 다듬어나가고 있습니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!