[모모] 리액트에서 파일 업로드하기(사용자 프로필 사진 바꾸기)
모모 프로젝트에서는 회원정보의 프로필사진, 닉네임, 지역, 자기소개 내용을 수정할 수 있습니다.
이 중에 프로필 사진 변경 (사용자가 파일을 직접 업로드하는 경우) 기능을 포스팅해 보겠습니다.
우선 사용자는 input에 파일을 업로드할 수 있어야 합니다.
<ImgEditButton>
<label className="editLabel" htmlFor="file-input">
<img src={imgEdit} alt="img-edit-button" />
</label>
<ImgEditInput
id="file-input"
accept="image/png, image/gif, image/jpeg"
type="file"
onChange={handleImgChange}
/>
</ImgEditButton>
type 속성은 "file"로 설정해 줍니다.
accept 속성은 허용할 확장자 명을 입력해 줍니다.
💡 파일 업로드 하는 부분을 못생긴 input창이 아닌 이미지를 보여주고 싶다면 <lable> 안에 위치시키면 됩니다.
💡 만약 여러 개의 파일을 업로드하고 싶으시면 multiple 속성을 추가해 주시면 됩니다.
<input type="file" multiple />
input에서 파일을 업로드하여 변경이 일어나면
이미지를 변경하는 함수 handleImgChange를 실행시켜줍니다.
다음은 handleImgChange 함수에 대한 내용입니다.
프로젝트에서는 사용자가 프로필 사진 변경 시
수정 완료 요청을 보내기 전에
수정 페이지에서 업로드된 사진을 미리 보기 할 수 있게 구현하였습니다.
//handleImgChange
const input = event.target;
if (input.files) {
const file = input.files[0]; // 파일 추출
}
event객체의 target의 files 속성에 사용자가 업로드한 파일이 들어있습니다.
console.log(input.files)를 찍어보면 아래와 같이 업로드한 파일을 확인할 수 있습니다.
const reader = new FileReader();
파일과 관련된 작업을 수행하는 FileReader 생성자 함수를 호출해 줍니다.
reader.onload = function (e) {
const imageDataURL = e.target?.result as string;
setUploadedImage(imageDataURL);
};
reader.readAsDataURL(file);
1. onload 이벤트 핸들러함수를 정의합니다.
2. e 객체를 통해 파일의 Data URL을 얻어와서
미리 보기를 할 수 있도록 상태로 저장해 둡니다. setUploadedImage(imageDataURL)
3. readAsDataURL 메소드로 위에서 추출한 파일을 읽고 2.로 돌아가 Data URL로 변환하고
onload 이벤트 핸들러를 호출하여 Data URL을 얻을 수 있습니다.
console.log(imageDataURL)을 찍어보면 아래와 같은 어마무시하게 긴 문자열들을 확인할 수 있습니다.
setUploadedFile(file);
마지막으로 서버에 전송하기 위해 파일을 상태로 저장해 줍니다.
//userEdit.tsx
const [uploadedImage, setUploadedImage] = useState<string>('');
const [uploadedFile, setUploadedFile] = useState<File | null | string>(null);
const handleImgChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const input = event.target;
if (input.files) {
const file = input.files[0]; // 파일 추출
const reader = new FileReader();
reader.onload = function (e) {
const imageDataURL = e.target?.result as string;
setUploadedImage(imageDataURL);
};
reader.readAsDataURL(file); // 추출한 파일을 읽어 데이터 URL로 변환
setUploadedFile(file); // 파일 상태 변수에 저장
}
};
// 중략
<ImageContainer>
{uploadedImage ? (
<ProfileImage src={uploadedImage} />
) : (
<ProfileImage src={myData?.profileImage || profile} />
)}
<ImgEditButton>
<label className="editLabel" htmlFor="file-input">
<img src={imgEdit} alt="img-edit-button" />
</label>
<ImgEditInput
id="file-input"
accept="image/png, image/gif, image/jpeg"
type="file"
onChange={handleImgChange}
/>
</ImgEditButton>
</ImageContainer>
서버에 patch 요청하는 함수는 다음과 같습니다.
data.memberPatchDto는 문자열 값을 가지는 유저정보 객체이고
data.file은 유저가 업로드한 이미지 파일입니다.
export const patchMyDataImg = async (url: string, data: EditMember) => {
const formData = new FormData();
if (data.file) {
formData.append('image', data.file);
}
const jsonBlob = new Blob([JSON.stringify(data.memberPatchDto)], {
type: 'application/json',
});
formData.append('memberPatchDto', jsonBlob);
const headers = {
'Content-Type': 'multipart/form-data',
};
const res = await instance.patch(url, formData, { headers });
return res.data;
};
https://developer.mozilla.org/en-US/docs/Web/API/FileReader/FileReader
FileReader: FileReader() constructor - Web APIs | MDN
The FileReader() constructor creates a new FileReader.
developer.mozilla.org
https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob
Blob: Blob() constructor - Web APIs | MDN
The Blob() constructor returns a new Blob object. The content of the blob consists of the concatenation of the values given in the parameter array.
developer.mozilla.org
https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData
FormData: FormData() constructor - Web APIs | MDN
The FormData() constructor creates a new FormData object.
developer.mozilla.org