벨로그의 설정 페이지를 보면, 위 이미지와 같이 썸네일 이미지, 이미지 업로드 버튼 / 이미지 제거 버튼이 있다.
이처럼 클라이언트 앱에서 이미지 업로드 버튼을 클릭해 이미지를 선택하고, 서버를 통해 클라우드 스토리지(오브젝트 스토리지)에 이미지 파일을 업로드해보자.
<input type="file">
요소는 사용자가 장치 저장소에서 하나 이상의 파일을 선택할 수 있도록 해준다. 선택된 파일은 폼 제출을 사용해 서버에 업로드 하거나, javascript code 또는 File API 를 사용해 조작할 수 있게 된다.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file
<input type="file">
로 버튼을 만들고, accept='image/*'
속성을 줘서 이미지 파일 유형을 가진 파일만 업로드 할 수 있도록 해준다.
그리고 input 을 숨기고, 이미지 업로드 버튼에 연결시켜 주기 위해 2가지 방법을 시도해보았다.
1) <label>
을 <input>
요소에 linking 하기
<label>
을 클릭해서<input>
요소 자체에 초점을 맞추거나 활성화를 시킬 수 있다.<label>
을<input>
요소와 연관시키려면,<input>
에 id 속성을 넣고,<label>
에 id 와 같은 값의for
속성을 넣어야한다.- React 의 경우
htmlFor
속성으로 넣어줘야한다.<label>
안에<input>
을 중첩시킬 경우 연관이 암시적이므로 for 및 id 속성이 필요없다.
https://developer.mozilla.org/ko/docs/Web/HTML/Element/label
나의 경우에는 이미지 업로드 버튼이 button tag 로 이루어진 컴포넌트 였기에, <label>
로 이미지 업로드 버튼을 감쌌더니 linking 이 되지 않았다. <label>
이 <button>
과 중첩되어 있을 경우 <input>
과 linking 되지 않는 것 같다.
두번째 방법으로 <label>
로 <input>
을 감싸고 버튼과 겹쳐지도록 css 로 위치조절을 해줬는데, <label>
이 버튼 위에 있으면 hover effect 가 발생하지 않았고, <label>
이 버튼 밑에 있으면 장치 관리자 창이 생기지 않았다.
2) useRef 사용하기
<input>
요소를 display: none
으로 없애고, 이미지 업로드 onClick 이벤트 핸들러에서 useRef 로 참조하고있는 <input>
요소에 onClick 이벤트를 호출해 장치 관리자 창을 띄웠다.
그리고 파일을 선택하면 <input>
요소의 onChange
이벤트가 호출되는데, onChange 이벤트 핸들러에서 e.target.files
를 통해 선택한 파일 객체에 대한 정보를 알 수 있게 된다.
// SettingUserThumbnail.tsx
const SettingUserThumbnail = () => {
const inputRef = useRef<HTMLInputElement | null>(null);
const onUploadImage = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) {
return;
}
console.log(e.target.files[0].name);
}, []);
const onUploadImageButtonClick = useCallback(() => {
if (!inputRef.current) {
return;
}
inputRef.current.click();
}, []);
return (
<input type="file" accept="image/*" ref={inputRef} onChange={onUploadImage} />
<Button label="이미지 업로드" onClick={onUploadImageButtonClick} />
);
}
이미지 파일을 폼 제출을 사용해 서버에 업로드 하기 위해 FormData 객체를 사용한다. key 값과 value 쌍의 형태로 데이터 전송을 도와준다.
https://developer.mozilla.org/ko/docs/Web/API/FormData
https://developer.mozilla.org/ko/docs/Web/API/FormData/FormData
// SettingUserThumbnail.tsx
const SettingUserThumbnail = () => {
const onUploadImage = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) {
return;
}
const formData = new FormData();
formData.append('image', e.target.files[0]);
axios({
baseURL: API_HOST,
url: '/images/:username/thumbnail',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
}, []);
const onUploadImageButtonClick = useCallback(() => {
if (!inputRef.current) {
return;
}
inputRef.current.click();
}, []);
return (
<input type="file" accept="image/*" ref={inputRef} onChange={onUploadImage} />
<Button label="이미지 업로드" onClick={onUploadImageButtonClick} />
);
}
single('name') rhk input name 이 일치하지 않을 때 발생한다.
const SettingUserThumbnail = () => {
// 기존 코드 중략
return (
<SettingUserThumbnailLayout>
<input
type="file"
accept="image/*"
name="thumbnail"
ref={inputRef}
onChange={onUploadImage}
/>
<Button label="이미지 업로드" onClick={onUploadImageButtonClick} />
<Button label="이미지 제거" onClick={onDeleteImage} />
</SettingUserThumbnailLayout>
);
};
// 사용자 썸네일 이미지 업로드
router.post('/images/:username/thumbnail', upload.single('thumbnail'), (req, response) => {
// 코드 중략
);
});
깃헙 공유 가능한가요??.. 🙏🙏