Framework/Next.js

[Next.js] 비동기로 파일을 여러 개 업로드 할 때 콜백 처리하기 (ft. axios)

taedonn 2023. 10. 13. 15:39
728x90

Next.js

 

 

내용

비동기로 파일을 여러개 업로드 할 때, 마지막 파일의 업로드가 완료되면 콜백을 실행해야하는 경우가 있다. 이럴 때 파일 업로드 상황을 파악하기 위해 쓸데없이 코드가 길어질 수 있는데, 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
}));

 

 

함수 설계

  1. progress를 담을 state를 지정한다.
  2. input에 파일이 업로드되면 파일의 개수만큼 axios.post()를 실행한다.
  3. axios의 onUploadProgress에서 전달받은 값을 progress에 저장한다.
  4. 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

사용법 | Axios 러닝 가이드

728x90