파일 업로드는 현대 웹 애플리케이션에서 필수적인 기능 중 하나입니다. 이번 글에서는 React를 사용하여 실시간 업로드 진행 상황을 표시하는 파일 업로드 컴포넌트를 만들어보겠습니다. 스타일링과 함께 각 기능을 자세히 설명하고, 보완할 수 있는 점에 대해서도 고려해보겠습니다.
먼저 파일 업로드를 담당할 UploadFile 컴포넌트를 만들어보겠습니다. 이 컴포넌트는 사용자가 파일을 선택하고 업로드할 수 있는 환경을 제공할 것입니다.
import React, { useState, useRef } from "react";
import styled from "styled-components";
import axios from "axios";
/**
* 파일 업로드 컴포넌트
* @param {Function} onError - 업로드 중 발생한 에러 처리 함수
* @param {Function} onUploadProgress - 업로드 진행 상황 처리 함수
* @returns {JSX.Element} 파일 업로드 컴포넌트
*/
const UploadFile = ({ onError, onUploadProgress }) => {
// 선택된 파일 목록과 업로드 진행 상태를 관리하는 상태 변수들
const [selectedFiles, setSelectedFiles] = useState([]);
const [uploadProgress, setUploadProgress] = useState(0);
// 파일 입력(input) 요소에 대한 참조를 생성
const fileInputRef = useRef(null);
// 파일 선택 및 업로드 처리 함수
const handleFileChange = async (e) => {
// 파일 선택 시 발생하는 이벤트 핸들러
const files = Array.from(e.target.files);
setSelectedFiles(files);
// 선택된 각 파일에 대해 순회하며 업로드 진행
for (let file of files) {
try {
// AJAX를 통한 파일 업로드 요청
const formData = new FormData();
formData.append("file", file);
await axios.post("/upload", formData, {
// 업로드 진행 상황을 감지하여 프로그레스 바 업데이트
onUploadProgress: (progressEvent) => {
const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
setUploadProgress(progress);
onUploadProgress(progress);
}
});
} catch (error) {
// 파일 업로드 실패 시 에러 처리
onError(error);
}
}
};
// 파일 선택 아이콘 클릭 시 파일 입력(input) 요소 클릭
const handleIconClick = (e) => {
e.stopPropagation(); // 부모 요소로의 이벤트 전파 차단
fileInputRef.current.click(); // 파일 입력(input) 요소 클릭
};
return (
<FileUploadWrapper>
<UploadContainer>
{selectedFiles.length > 0 && (
{/* 선택된 파일 목록을 표시 */}
<FileList>
{selectedFiles.map((file, index) => (
<FileName key={index}>{file.name}</FileName>
))}
</FileList>
{/* 파일 선택 아이콘 */}
<IconUploadClipContainer onClick={handleIconClick}>
{/* 아이콘 이미지 또는 아이콘 컴포넌트 */}
<IconUploadClip />
</IconUploadClipContainer>
{/* 파일 입력(input) 요소 */}
<FileInput
ref={fileInputRef}
type="file"
id="fileUpload"
onChange={handleFileChange}
accept="image/*"
multiple
/>
</UploadContainer>
{/* 업로드 진행 상황을 나타내는 프로그레스 바 */}
<ProgressBar style={{ width: `${uploadProgress}%` }} />
</FileUploadWrapper>
);
};
export default UploadFile;
// 스타일드 컴포넌트 정의
const FileUploadWrapper = styled.div`
/* 파일 업로드 컴포넌트 전체 스타일 */
width: 100%;
`;
const UploadContainer = styled.div`
position: relative;
flex: 1;
height: 2.75rem;
padding: 0.5rem 1rem;
background: #f2f2f2;
border-radius: 0.25rem;
display: flex;
align-items: center;
`;
const FileList = styled.div`
/* 선택된 파일 목록에 대한 스타일 */
margin-right: 10px;
`;
const FileName = styled.div`
/* 파일 이름에 대한 스타일 */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`;
const IconUploadClipContainer = styled.span`
/* 파일 선택 아이콘에 대한 스타일 */
position: absolute;
right: 1rem;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
`;
const FileInput = styled.input`
/* 파일 입력(input) 요소에 대한 스타일 */
display: none;
`;
const ProgressBar = styled.div`
/* 업로드 진행 상태를 나타내는 프로그레스 바에 대한 스타일 */
position: absolute;
bottom: 0;
left: 0;
width: ${uploadProgress}%;
height: 3px;
background-color: #007bff;
transition: width 0.3s ease;
`;
위의 코드에선 파일목록 까지 파일 업로드 컴포넌트에 담았지만 만약 따로 파일 목록을 컴포넌트화 한다면
/**
* 파일 목록 컴포넌트
* @param {Array} files - 선택된 파일 목록
* @returns {JSX.Element} 파일 목록 컴포넌트
*/
const FileList = ({ files }) => (
<FileListContainer>
{/** 선택된 파일 목록을 표시 */}
{files.map((file, index) => (
<FileName key={index}>{file.name}</FileName>
))}
</FileListContainer>
);
// 파일 목록 컴포넌트의 스타일드 컴포넌트 정의
const FileListContainer = styled.div`
/* 파일 목록 컴포넌트 전체 스타일 */
`;
// 파일 이름에 대한 스타일드 컴포넌트 정의
const FileName = styled.div`
/* 파일 이름에 대한 스타일 */
`;
으로 만들 수 있겠습니다.
위 코드는 다음과 같은 주요 기능과 보완점을 가지고 있습니다:
파일 업로드 및 진행 상황 표시: handleFileChange 함수를 통해 파일을 선택하고 업로드하며, 진행 상황은 프로그레스 바로 표시됩니다. 프로그레스 바는 업로드 상태에 따라 동적으로 변화합니다.
파일 선택 아이콘 기능: 파일 선택 아이콘을 클릭하면 실제 파일 선택 창이 열리도록 처리됩니다.
파일 목록 표시: 선택된 파일 목록이 화면에 표시됩니다. 각 파일의 이름은 가로로 표시되며, 너무 긴 파일 이름은 생략됩니다.
스타일링: 각 요소들은 styled-components를 활용하여 스타일이 적용되었습니다. 이를 통해 컴포넌트의 외관과 레이아웃을 손쉽게 조정할 수 있습니다.
보완점: 파일 목록이 가로로 너무 길어질 경우 화면 크기를 넘어가는 문제가 있습니다. 이를 해결하기 위해 파일 이름이 적당한 너비를 가지고 자동으로 줄 바꿈되도록 스타일을 추가하는 것이 좋습니다.
전체 코드 및 부모 컴포넌트 활용: 이 컴포넌트는 부모 컴포넌트에서 사용될 것을 가정하고 작성되었습니다. 부모 컴포넌트에서는 UploadFile 컴포넌트를 렌더링하고, 필요한 콜백 함수들을 props로 전달하여 사용할 수 있습니다.
부모 컴포넌트에서의 사용 예시는 다음과 같습니다
const ParentComponent = () => {
const handleError = (error) => {
// 에러 처리 로직
};
const handleProgress = (progress) => {
// 업로드 진행 상황 처리 로직
};
return (
<div>
<h1>파일 업로드</h1>
<UploadFile onError={handleError} onUploadProgress={handleProgress} />
</div>
);
};
위와 같이 부모 컴포넌트에서 UploadFile 컴포넌트를 사용할 수 있습니다. 필요에 따라 에러 처리 및 업로드 진행 상황 처리 함수를 구현하고 전달하여 사용할 수 있습니다.
import React, { useState, useRef } from "react";
import styled from "styled-components";
import axios from "axios";
const UploadFile = ({ onError, onUploadProgress }) => {
const [selectedFiles, setSelectedFiles] = useState([]);
const fileInputRef = useRef(null);
const [uploadProgress, setUploadProgress] = useState(0);
// 파일 선택 및 업로드 처리 함수
const handleFileChange = async (e) => {
const files = Array.from(e.target.files);
setSelectedFiles(files);
for (let file of files) {
try {
// 파일 업로드 AJAX 요청
const formData = new FormData();
formData.append("file", file);
await axios.post("/upload", formData, {
onUploadProgress: (progressEvent) => {
const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
setUploadProgress(progress);
onUploadProgress(progress);
}
});
} catch (error) {
onError(error);
}
}
};
// 파일 선택 아이콘 클릭 시 처리하는 함수
const handleIconClick = (e) => {
e.stopPropagation();
fileInputRef.current.click();
};
return (
<FileUploadWrapper>
{/* 파일 목록 및 아이콘 */}
{/* 프로그레스 바 */}
</FileUploadWrapper>
);
};
export default UploadFile;
return (
<UploadContainer>
{selectedFiles.length > 0 && (
<FileList>
{selectedFiles.map((file, index) => (
<FileName key={index}>{file.name}</FileName>
))}
</FileList>
)}
<IconUploadClipContainer onClick={handleIconClick}>
{/* 업로드 아이콘 */}
</IconUploadClipContainer>
<FileInput
ref={fileInputRef}
type="file"
id="fileUpload"
onChange={handleFileChange}
accept="image/*"
multiple
/>
</UploadContainer>
);
return (
<FileUploadWrapper>
{/* 파일 목록 및 아이콘 */}
{/* 프로그레스 바 */}
{uploadProgress > 0 && <ProgressBar />}
</FileUploadWrapper>
);
// 파일 선택 및 업로드 처리 함수
const handleFileChange = async (e) => {
const files = Array.from(e.target.files);
setSelectedFiles(files);
setUploadProgress(0); // 업로드 진행 상황 초기화
for (let file of files) {
try {
// 파일 업로드 AJAX 요청
const formData = new FormData();
formData.append("file", file);
await axios.post("/upload", formData, {
onUploadProgress: (progressEvent) => {
const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
setUploadProgress(progress);
onUploadProgress(progress);
}
});
} catch (error) {
onError(error); // 에러 처리
}
}
};
const FileUploadWrapper = styled.div`
width: 100%;
`;
const UploadContainer = styled.div`
position: relative;
flex: 1;
height: 2.75rem;
padding: 0.5rem 1rem;
background: #f2f2f2;
border-radius: 0.25rem;
display: flex;
align-items: center;
`;
const FileList = styled.div`
margin-right: 10px;
`;
const FileName = styled.div`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`;
const IconUploadClipContainer = styled.span`
position: absolute;
right: 1rem;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
`;
const FileInput = styled.input`
display: none;
`;
const ProgressBar = styled.div`
position: absolute;
bottom: 0;
left: 0;
width: ${uploadProgress}%;
height: 3px;
background-color: #007bff;
transition: width 0.3s ease;
`;
현재는 파일 선택 후 즉시 업로드가 이루어지지만, 선택한 파일들의 유효성을 먼저 검사하여 필요한 경우에만 업로드를 수행하도록 개선할 수 있습니다.
사용자 경험을 향상시키기 위해 파일 업로드 상태에 따라 UI를 보다 풍부하게 표현할 수 있습니다. 예를 들어, 업로드 완료 시에는 성공 메시지를 표시하거나, 에러 발생 시에는 에러 메시지와 함께 재시도 버튼을 제공할 수 있습니다.
스타일링을 더욱 개선하여 사용자가 직관적으로 파일 업로드 영역을 인식할 수 있도록 할 수 있습니다. 예를 들어, 드래그 앤 드롭 기능을 추가하거나, 마우스 오버 효과를 적용할 수 있습니다.
이렇게 파일 업로드에 대해 알아 보는 과정을 통해 프로젝트에 필요한 파일 업로드 기능을 구현하는 데 도움이 되기를 바랍니다.