input 태그만 사용 시 문제점
<input type="file" />
위 코드처럼 input 태그를 사용하면, 파일 업로드를 위한 UI가 화면에 생성됩니다.
그러나 위 UI를 화면에 표시하고 싶지 않은 경우가 있습니다.
input 엘리먼트를 화면에서 숨기되, Button 엘리먼트를 통해서 파일을 업로드하는 코드를 구현해보겠습니다.
파일 업로드 코드 구현
1. display:none 스타일 속성으로 input 엘리먼트를 화면에서 숨깁니다.
<input
type="file"
ref={inputRef}
style={{ display: "none" }}
onChange={handleFileChange}
/>
2. useRef를 사용하여 <input>을 참조하고, 버튼 클릭 시 <input>에 대한 클릭이벤트가 발생하도록 합니다.
useRef를 사용하면 버튼 엘리먼트에서 onClick 이벤트가 발생했을 때 input 태그가 동작하도록 만들 수 있습니다.
input 태그에 클릭 이벤트를 발생시키는 함수를 제작하고, 이를 버튼 엘리먼트의 onClick 이벤트 핸들러 함수로 추가합니다.
const inputRef = useRef<HTMLInputElement>(null);
const handleUploadButtonClick = () => {
if (inputRef.current) {
inputRef.current.click();
}
};
...
<button
disabled={isUploading}
className="file-upload"
onClick={handleUploadButtonClick}
>
파일 업로드
</button>
3. <input>의 onChange 이벤트를 통해 파일 정보를 state에 저장합니다.
file 데이터를 저장할 state를 선언합니다.
input 엘리먼트에 onChange 이벤트함수를 등록하여 업로드된 파일을 state에 저장합니다.
const [file, setFile] = useState<File | undefined>(undefined);
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files[0]) {
setFile(event.target.files[0]);
}
};
<input
type="file"
ref={inputRef}
style={{ display: "none" }}
onChange={handleFileChange}
/>
4. axios를 통해 파일 데이터, multipart/form-data 헤더, onUploadProgress 콜백함수를 전달합니다.
파일 데이터를 AJAX 요청으로 서버에 전달하는 함수를 제작합니다.
axios의 request body에 file 데이터를 입력해줍니다.
헤더의 content-type은 body 데이터의 종류를 명시하며 여기서는 multipart/form-data를 입력합니다.
multipart/form-data는 요청에서 바이너리 데이터를 인코딩하는 방법으로
바이너리 데이터가 포함된 파일을 업로드하거나 양식을 제출할 때 사용됩니다.
axios의 onUploadProgess 콜백함수를 통해 진행상황에 대한 정보를 받아 화면에 표시할 수 있습니다.
onUploadProgress 함수는 파일 업로드 중에 주기적으로 호출되며 업로드 진행률에 대한 정보가 포함된 이벤트 객체를 받습니다.
const [file, setFile] = useState<File | undefined>(undefined);
const [isUploading, setIsUploading] = useState<boolean>(false);
const [progressPercent, setProgressPercent] = useState<number>(0);
const handleUploadButtonClick = () => {
if (inputRef.current) {
inputRef.current.click();
}
};
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files[0]) {
setFile(event.target.files[0]);
}
};
const handleSubmitFile = async () => {
if (file) {
const formData = new FormData();
formData.append("file", file); // formData에 file 데이터 넣어주기 (key값은 "file"이지만 다른 값으로 변경 가능. 단 서버와 코드를 맞춰야 함)
try {
setIsUploading(true);
const headers = {
"Content-Type": "multipart/form-data",
};
const onUploadProgress = (event: ProgressEvent) => {
const per = (event.loaded / event.total) * 100;
setProgressPercent(parseFloat(per.toFixed(2)));
};
await axios.post(`${serverURL}/api/upload`, formData, {
headers,
onUploadProgress,
});
setFile(undefined);
setIsUploading(false);
} catch (err) {
setFile(undefined);
setIsUploading(false);
alert("파일 업로드중 문제가 발생하였습니다");
}
}
};
5. 파일이 전송중인 경우 진행상황 % 데이터를 화면에 표시합니다.
<progress
style={{
display: `${isUploading ? "block" : "none"}`,
}}
>
{progressPercent}%
</progress>
전체 코드
import axios, { AxiosResponse } from "axios";
import serverURL from "../utils/ServerURL";
import React, { useEffect, useRef, useState } from "react";
export default function Test() {
const [file, setFile] = useState<File | undefined>(undefined);
const [isUploading, setIsUploading] = useState<boolean>(false);
const [progressPercent, setProgressPercent] = useState<number>(0);
const inputRef = useRef<HTMLInputElement>(null);
const handleUploadButtonClick = () => {
if (inputRef.current) {
inputRef.current.click();
}
};
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files[0]) {
setFile(event.target.files[0]);
}
};
const handleSubmitFile = async () => {
if (file) {
const formData = new FormData();
formData.append("file", file);
// formData에 file 데이터 넣어주기 (key값은 "file"이지만 다른 값으로 변경 가능. 단 서버와 코드를 맞춰야 함)
try {
setIsUploading(true);
const headers = {
"Content-Type": "multipart/form-data",
};
const onUploadProgress = (event: ProgressEvent) => {
const per = (event.loaded / event.total) * 100;
setProgressPercent(parseFloat(per.toFixed(2)));
};
await axios.post(`${serverURL}/api/upload`, formData, {
headers,
onUploadProgress,
});
setFile(undefined);
setIsUploading(false);
} catch (err) {
setFile(undefined);
setIsUploading(false);
alert("파일 업로드중 문제가 발생하였습니다");
}
}
};
useEffect(() => {
if (file) {
handleSubmitFile();
}
}, [file]);
return (
<>
<button
disabled={isUploading}
className="file-upload"
onClick={handleUploadButtonClick}
>
파일 업로드
</button>
<input
type="file"
ref={inputRef}
style={{ display: "none" }}
onChange={handleFileChange}
/>
<progress
style={{
display: `${isUploading ? "block" : "none"}`,
}}
>
{progressPercent}%
</progress>
</>
);
}
'프로그래밍 > 웹 개발' 카테고리의 다른 글
[React, TypeScript] 브라우저 화면에서 특정 단어 검색 기능 구현 (0) | 2023.04.20 |
---|---|
[React] 파일 다운로드(download) 및 헤더(header) 정보 (0) | 2023.04.18 |
[react-color, TypeScript] 컬러 선택 기능 구현하기 (color picker) (0) | 2023.04.08 |
[SCSS, JS] JavaScript를 통해 SCSS 속성값 동적으로 연결하기 (0) | 2023.04.08 |
[EC2] sudo node 명령어 에러 (The Node-API version of this Node instance is 1) (0) | 2022.09.30 |
댓글