자바스크립트에서 file을 upload 하기 위해서는 input
태그를 type="file"
로 해서 사용한다.
이때 file을 upload하면 onChange event가 발생하는데, 같은 파일을 다시 upload할 경우 event가 trigger되지 않는다. (onChange는 실질적인 내용 변화에만 trigger되기 때문!)
따라서 file이 upload되면 값을 초기화해주어야 한다. 내용물을 담고 있는 e.target.value
를 초기화해주자!
// react-hook-form을 함께 사용
<Controller
name="profileImage"
control={control}
render={({ field }) => {
return (
<input
accept="image/*"
id="profile-image-input" // label htmlFor
type="file" // type="file"
hidden // input을 숨기고 다른 스타일링위해
onChange={e => {
handleProfileImageChange(e);
}}
/>
);
}}
/>
<label htmlFor="profile-image-input" className="photo-icon">
<IconButton variant="outlined" color="primary" component="span">
<AddAPhotoIcon />
</IconButton>
</label>
input
의 onChange
부분을 보면 다음 함수를 실행중이다.
// image preview위한 state
const [loadedProfileImage, setLoadedProfileImage] = useState({
imagePreviewUrl: '',
imageBlob: null,
});
let reader = new FileReader(); // FileReader API로 이미지 인식
const handleProfileImageChange = e => {
e.preventDefault();
const file = e.target.files[0]; // file object는 e.target.files[0]에 있다.
if (file) {
reader.readAsDataURL(file); // 1. reader에게 file을 먼저 읽히고
// 사진 올리고 나서 처리하는 event
reader.onloadend = () => {
setLoadedProfileImage({ imagePreviewUrl: reader.result });
dispatch(triggerImageCropModal()); // 사진 업로드 하면 crop창 띄움
e.target.value = ''; // 💖 같은 파일을 올리면 인지못해서 여기서 초기화
}; // 2. 비동기적으로 load가 끝나면 state에 저장
}
};
file 객체는 위와 같이 구성되어있다.
이 file 객체를 reader.readAsDataURL
을 통해 읽는다. MDN에 따르면
readAsDataURL 메서드는 컨텐츠를 특정 Blob 이나 File에서 읽어 오는 역할을 합니다. 읽어오는 read 행위가 종료되는 경우에, readyState (en-US) 의 상태가 DONE이 되며, loadend (en-US) 이벤트가 트리거 됩니다. 이와 함께, base64 인코딩 된 스트링 데이터가 result 속성(attribute)에 담아지게 됩니다.
이제 img
태그의 src에 loadedProfileImage.imagePreviewUrl
을 담으면 preview로 볼 수 있다.