[Next.js] 비동기로 파일을 여러 개 업로드 할 때 콜백 처리하기 (ft. axios)
내용
비동기로 파일을 여러개 업로드 할 때, 마지막 파일의 업로드가 완료되면 콜백을 실행해야하는 경우가 있다. 이럴 때 파일 업로드 상황을 파악하기 위해 쓸데없이 코드가 길어질 수 있는데, axios에서는 이런 상황에서 쓸 수 있게 axios.all()이라는 메서드를 제공하고 있다. 이번 포스트에는 간단한 예시와 함께 axios.all() 사용법과 파일 업로드 progress를 구현하는 법을 정리했다.
설치 패키지 및 버전
"dependencies": {
"next": "13.4.1",
"axios": "^1.4.0",
"typescript": "5.0.4",
}
레이아웃 구성
먼저 간단하게 기능을 구현하기 위해 <input type="file"/>을 넣고, multiple 속성을 지정해 줬다. input에는 multiple 속성을 넣어줘야 파일을 여러 개 업로드할 수 있다. 그 아래에는 파일 업로드 진행 상황을 표시하기 위한 빈 div를 만들었다. 실제 코드는 더 길겠지만, 가독성을 위해 스타일 관련된 부분은 과감히 생략.
<input type="file" onChange={handleChange} multiple id="my-file"/>
<div style={{width: `${progress}%`}}></div>
axios.all()
axios.all()은 axios에서 여러 개의 요청을 동시에 수행(멀티 리퀘스트)할 경우 사용하는 메서드다. 멀티 리퀘스트를 보내야 하는 상황이 바람직하지 않다고는 하지만,, 어느 정도는 괜찮지 않을까,, 그래도 언제나 클라이언트단에서 API 요청은 최소화하는 게 좋다.
axios.all() 사용 예시
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// Both requests are now complete
}));
함수 설계
- progress를 담을 state를 지정한다.
- input에 파일이 업로드되면 파일의 개수만큼 axios.post()를 실행한다.
- axios의 onUploadProgress에서 전달받은 값을 progress에 저장한다.
- axios.post() 실행이 모두 완료되면 axios.all()로 콜백을 실행한다.
전체 코드 예시
const [progress, setProgress] = useState<number>(0);
const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
let allPromise = [];
let percentage = 0;
// 전체 파일 크기 계산
let totalSize = 0;
for (let i = 0; i < e.target.files.length; i++) {
totalSize += Number(e.target.files[i].file.size);
}
// 이미지 개수만큼 axios.post() 실행
for (let i = 0; i < e.target.files.length; i++) {
let promise = await axios.post("API 경로", data, {
// progress return 받기
onUploadProgress: (progressEvent: AxiosProgressEvent) => {
if (progressEvent && progressEvent.loaded && progressEvent.total) {
// 기존 progress + (파일크기/전체파일크기)를 progress에 저장
percentage = percentage + Number(Math.round(e.target.files[i].file.size) / totalSize * 100);
setProgress(percentage);
}
},
headers: {
"Context-Type": "multipart/form-data"
}
})
.then(() => console.log(`이미지 ${i} 업로드 성공`))
.catch(() => console.log(`이미지 ${i} 업로드 실패`));
// allPromise에 promise 저장
allPromise.push(promise);
}
// axios.post() 실행이 모두 완료되면 axios.all() 실행
await axios.all(allPromise)
.then(() => console.log("이미지 모두 업로드 성공"))
.catch(() => console.log("이미지 모두 업로드 실패"));
}
}
여러 개의 파일을 업로드하는 경우 시간이 오래 걸리기 때문에, 진행 상황을 화면에 표시하는 게 좋을 거 같아서 파일을 따로따로 업로드한 후, progress를 return 받는 식으로 구현했다. progress는 기존에 지정된 progress + (파일 크기/전체 파일 크기)로 나온 값을 지정해 줬고, progress가 늘어날 때마다 아까 레이아웃에 넣은 div의 넓이가 증가하는 방식으로 클라이언트에서 진행 상황을 볼 수 있게 했다.
참고
파일의 개수만큼 API 요청을 보내는 게 비효율적인 것 같지만, 구글링해도 다른 방법이 딱히 나오지 않아서,, 일단은 이런 방식도 있구나 정도로 생각해도 좋을 거 같다.
레퍼런스
<input type="file"/> - HTML: Hypertext Markup Language | MDN