현재 코드는 기본적인 기능만 가능합니다. 사용자 경험을 고려하면 몇가지 버그와 불편함이 있기 때문에 개선이 필요합니다.
코드 설명
통신
통신은 axios를 사용합니다. axios에서는 'onUploadProgress'라는 속성을 사용할 수 있습니다. 이 속성은 axios를 통해 전송되는 파일의 진행상태를 추적할 수 있습니다. 저는 formData를 생성하고 append 메서드로 file에 대한 정보를 추가했습니다. 이후 axios 통신을 통해 formData를 보내면 서버로 업로드한 파일이 전송됩니다.
참고로 업로드에 성공했을 때, 들어오는 response에 백엔드 개발자분께서 message라는 키값으로 "{N} 개의 파일을 업로드하였습니다!" 라는 텍스트를 담아주셨습니다. 저는 그 메세지를 alert창으로 띄워서 사용자에게 업로드 완료을 육안으로 쉽게 알아차릴 수 있게 만들 수 있었습니다!
formData
formData에는 input type={file} 에 달려있는 onChange를 통해 실행되는 changeFile 함수를 통해 서버로 보낼 파일들이 담기게 됩니다. 여기서 파일을 여러개 선택하고 업로드하고 싶다면 input 속성에 mutiple={true}를 추가하시면 됩니다.
input을 통해 파일을 선택하셨으면 선택된 파일들은 chageFile함수의 for문을 통해, append 메서드를 통해 key,value를 가진 객체형태로 formData에 차곡차곡 담기게 됩니다. 서버에서 요청한 key값과 보낼 value를 매치해서 담으시면 됩니다.
ProgressBar
progressBar는 두가지의 데이터가 필요합니다. 현재 업로드될 파일의 총량과 현재 업로드가 된 데이터의 양!
axios의 속성인 onUploadProgress 를 통해 알 수 있습니다.
onUploadProgress: (e) => {
// axios의 onUploadProgress 속성은 파일 업로드 진행 상황을 추적할 수 있는 콜백 함수
let percent = (e.loaded / e.total) * 100;
progressRef.current.value = Math.round(percent);
statusRef.current.innerHTML = `${Math.round(percent)}% uploaded...`;
setContent(progressRef.current.value);
업로드가 몇 퍼센트 진행 됐는지 나타낼 코드입니다.
onUploadProgress 함수의 파라미터로 들어온 e 는 파일입니다. let percent 는 업로드 된 파일의 양/파일의 총량 으로 정리되게 되고. 그것을 가독성있도록 변환하는 것이 progressRef.current.value = Math.round(percent) 입니다. 예를 들어 파라미터로 들어온 파일의 크기가 100Mb 라고 한다면, 전송 버튼을 누른 뒤, 25Mb가 업로드 됐다. 그럼 percent는 25가 되는 것입니다. 이 percent을 이용해서 "25 % uploaded ... " 같은 텍스트를 동적으로 띄울 수 있으며, <ProgressPercent ref={progressRef} width={${content}%
} /> progressBar의 진행률을 보여줄 width 값도 동적으로 지정할 수 있습니다.
CORS 에러
정말정말 애를 먹었던 CORS 에러입니다. 저희는 서버에 데이터를 전송하는데 서로 다른 도메인에서 파일을 송신과 수신을 하려했었고, CORS 에러를 마주하게 됐습니다. 오랜 시간를 허비했지만, 해결방법은 간단했습니다.
'Access-Control-Allow-Origin' : 'http://localhost:3000',
'Access-Control-Allow-Headers' : 'Original,Content-Type,Authorization,X-Auth-Token',
'Access-Control-Allow-Methods' : 'GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS',
백엔드 코드의 헤더에 저희 프론트엔드가 사용하고 있었던 주소인 localhost:3000 의 송신을 허락한다는 명령어를 추가했습니다.
추가 구현이 필요해요!
현재 코드에서는 파일을 업로드하고 서버에 전송하는 플로우에는 문제가 없어보이지만, 사용자의 입장에서 보면 몇가지 헛점이 보입니다.
- 파일 업로드, 전송 후, 다시 전송을 하게 되면 0개의 파일 전송 (formData 초기화)
- 파일 선택 후, 다시 파일 선택창을 열었다가 취소하면, 기존의 선택된 파일 목록 초기화
- 선택된 파일 이름을 출력하도록 해야함
- 파일 전송 중단기능
이 기능들을 추가하면 사용자 경험에서 느낄 부족함이 없으리라 생각됩니다!
정리
input을 통해 파일이 선택되고, changeFile 함수를 통해 formData에 파일들이 담기게 됩니다. 이후 전송버튼을 눌러 onUploadProgress함수에 담겨있는 axios를 통해 서버에 파일이 전송되게 됩니다.
백엔드에서 "보내주실 때 파일이름 키값은 'fileJang'으로 주시고, 파일 타입은 'fileChan'으로 주세요" 라고 하셨다면?!formData.append('fileJang', file.name)
formData.append('fileChan', file.type)
이렇게 보내드리면 됩니다!
코드 내용
import React, { useState, useRef } from "react";
import styled from "styled-components";
import axios from "axios";
axios.defaults.withCredentials = true;
const Progress = () => {
const url = "통신할 주소!!";
// const [filesDiscription, setFilesDiscription] = useState([]);
const uploadRef = useRef();
const statusRef = useRef();
const loadTotalRef = useRef();
const progressRef = useRef();
const [content, setContent] = useState(0);
const source = axios.CancelToken.source(); // axios의 데이터 전송 취소 명령어
let formData = new FormData();
const changeFile = () => {
for (let i = 0; i < uploadRef.current.files.length; i++) {
const file = uploadRef.current.files[i];
formData.append("originalname", file.name); // 선택된 파일을 formData에 추가
formData.append("files", file); // 반복문을 활용하여 파일들을 formData 객체에 추가한다
formData.append("mimetype", file.type); // 선택된 파일을 formData에 추가
formData.append("totalAmountData", file.size); // 보내는 데이터의 총량 (bytes)
}
};
const postContents = () => {
if (formData === {}) {
alert("파일을 추가해주세요");
return;
} else {
axios({
url: url,
method: "post",
data: formData,
headers: { "Content-Type": "multipart/form-data" },
onUploadProgress: (e) => {
// axios의 onUploadProgress 속성은 파일 업로드 진행 상황을 추적할 수 있는 콜백 함수
let percent = (e.loaded / e.total) * 100;
progressRef.current.value = Math.round(percent);
statusRef.current.innerHTML = `${Math.round(percent)}% uploaded...`;
setContent(progressRef.current.value);
loadTotalRef.current.innerHTML = `uploaded ${e.loaded} bytes of ${e.total}`;
},
cancelToken: source.token,
})
.then((response) => {
console.log("Upload success", response);
alert(response.data.message);
statusRef.current.innerHTML = "Upload success!!";
})
.catch((error) => {
console.log("Upload failed", error);
statusRef.current.innerHTML = "Upload failed";
});
}
};
const AbortHandler = () => {
source.cancel(); // axios 데이터 전송 중단토큰 (서버와 합의 필요!)
console.log("Upload aborted");
statusRef.current.innerHTML = "Upload aborted";
};
return (
<div
className="App"
style={{
margin: "300px auto",
backgroundColor: "#c0c0c0",
width: "700px",
padding: "100px",
borderRadius: "85px",
}}
>
<input
type="file"
multiple={true}
name="file"
ref={uploadRef}
onChange={changeFile}
/>
<label
style={{
display: "flex",
justifyContent: "center",
position: "relative",
top: "15px",
}}
>
File progress:
<ProgressWrapper>
<ProgressPercent ref={progressRef} width={`${content}%`} />
</ProgressWrapper>
</label>
<p ref={statusRef} />
<p ref={loadTotalRef} />
<div
style={{
backgroundColor: "white",
borderRadius: "15px",
margin: "20px auto",
}}
>
</div>
<div
style={{
display: "flex",
justifyContent: "center",
}}
>
<button
onClick={postContents}
style={{
width: " 100px",
height: "50px",
fontSize: "16px",
}}
>
전송
</button>
<button
onClick={AbortHandler}
style={{
width: " 100px",
height: "50px",
fontSize: "16px",
}}
>
중단
</button>
</div>
</div>
);
};
export default Progress;
const ProgressWrapper = styled.div`
width: 500px;
height: 20px;
background-color: white;
margin: 3px 0 20px 10px;
border-radius: 15px;
`;
const ProgressPercent = styled.div`
width: ${(props) => props.width};
height: 100%;
border-radius: 15px;
background-color: lightblue;
`;
'Project' 카테고리의 다른 글
[ThreeJs] 개인 포트폴리오 제작기 #1 (2) | 2023.12.21 |
---|---|
[EmailJS] Contact 컴포넌트 제작 (0) | 2023.12.05 |
[OMS] 네이버 로그인 문제해결 (0) | 2023.06.17 |
[OMS] React 네이버 로그인 API 고생일지 (0) | 2023.06.15 |
[OMS] 프론트와 백, 누가 데이터를 갖고 있는게 좋을까? (0) | 2023.05.19 |