-
최근에 작업한 리액트 크롬 모멘텀(Momentum)에 이미지 업로드 기능을 추가해보고 싶어서 공부해 볼 겸
Cloudinary와 React-Dropzone을 연동하여 간단한 이미지 업로더 기능을 구현해 보았다!
혼자서 구글링하며 이것저것 만지작거리면서 겨우겨우 구현했는데.. 잘 구현한 건지는 모르겠다..😅
일단 오류 없이 동작되는 거에 만족하는 걸로...
(부족하거나 보완이 필요한 부분이 있다면 언제든 피드백 주세요)
내가 구현하고자 했던 사항은...
👉 이미지를 드래그 앤 드롭하여 단일 혹은 여러 개의 이미지를 한 번에 업로드하기
👉 업로드한 이미지를 저장해 두고, 해당 이미지들을 프론트단에 노출하기
이미지를 업로드하고 관리할 수 있는 기능을 제공하는 서비스는 많다. 처음에는 파이어베이스를 이용해보려고 했는데,
Cloudinary는 추가로 이미지를 커스터마이징(리사이징, 크롭핑, 이미지 최적화 등등)할 수 있는 여러 옵션들을 제공한다고 한다. 그래서 Cloudinary를 선택했다!
우선 Cloudinary를 사용하려면 회원가입을 해서 계정을 생성해야 한다.
회원가입 후 로그인 하고 일단 몇 가지 설정을 위해 오른쪽 상단에 톱니바퀴 아이콘을 클릭해 settings 페이지로 이동한다.
업로드와 관련된 설정을 해줄 것이므로 Settings > Upload 탭으로 이동한다.
스크롤을 내려서 보면 업로드 환경 설정을 할 수 있는 Upload presets 메뉴가 있다. 그리고 하단에 Add upload preset 버튼을 클릭해서 업로드 설정을 등록해야 한다.
Add upload preset 버튼을 클릭하면 설정페이지로 이동되고, 여기서 Signed mode가 Signed으로 설정되어 있는 부분을 Unsigned로 수정해 준다.
Signed mode는 로그인 후 인증이 완료된 사람들만 API를 사용할 수 있게 설정하는 것이다.
보안을 생각해서 인증된 사용자만 사용할 수 있게 하는 것이 좋겠지만, 나는 별도의 인증 없이 단순히 사진을 업로드하는 방식으로 구현할 것이기 때문에 Unsigned mode로 변경했다.
그리고 왼쪽 탭에서 Upload Manipulations 탭으로 이동하면 이미지 저장 포맷이나 변환 설정을 등록할 수 있다.
이미지 리사이즈 및 크롭, 포맷 등을 세세하게 설정할 수 있다.
설정이 끝나면 우측 상단의 주황색 Save 버튼을 클릭하여 설정을 저장해 주면 된다.
이제 해야 할 부분은 공식 문서를 보며 어떻게 적용해 나갈지를 파악해야 한다. Cloudinary 홈페이지 상단에 있는 물음표 버튼을 클릭하면 나오는 메뉴 중 Documentation이 공식문서 페이지이다.
우린 업로드 관련 문서를 볼 것이므로 Media Upload 페이지로 이동한다.
cloudinary는 REST API 혹은 SDK 방식으로 이용할 수 있다. 나는 사진을 업로드하는 단순한 기능을 구현할 것이므로 REST API 방식으로 했다.
REST API 방식의 문서를 보려면 좌측 메뉴에서 Media upload - Uploading assets 세션에서 확인할 수 있다.
그리고 upload using Cloudinary's REST API를 클릭하면 해당 문서로 이동된다.
문서 내용을 살펴보면 필요한 사항들이 잘 정리되어 있다.
우선 사진 업로드는 POST 메서드를 사용하고 요청 URL은 다음과 같다.
https://api.cloudinary.com/v1_1/<cloud name>/<resource_type>/upload
cloud name 은 대시보드에서 확인할 수 있는 클라우드 이름이다.
resource_type
은image
라고 입력해 주면 된다.그리고 Cloudinary로 전송하는 POST 요청의 내용은 인증된 요청인지 여부에 따라 요청에 대한 필수 매개변수가 다르다.
나는 인증되지 않은 요청으로 구현할 것이므로 아래의
file
,upload_preset
매개변수가 필요하다.여기서
file
은 실제로 업로드할 파일이고,upload_preset
은 settings 페이지에서 등록했던 preset 설정의 이름이다.upload_preset은 settings 페이지에서 등록했던 preset 설정의 이름이다. 이제 공식문서를 쭉쭉 읽으며 샘플 예제를 보면서 어떻게 사용하는지 파악하면 된다. 여기서 제공하는 예제를 살펴보면 아래와 같다.
Code explorer: Upload multiple files using a form (unsigned)
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <title>Upload example with response</title> </head> <body> <form method="post" enctype="multipart/form-data"> <input type="file" name="files[]" multiple> <input type="submit" value="Upload Files" name="submit"> </form> <p id="data"></p> <script src="./upload.js"></script> </body> </html>
const url = "https://api.cloudinary.com/v1_1/demo/image/upload"; const form = document.querySelector("form"); form.addEventListener("submit", (e) => { e.preventDefault(); const files = document.querySelector("[type=file]").files; const formData = new FormData(); for (let i = 0; i < files.length; i++) { let file = files[i]; formData.append("file", file); formData.append("upload_preset", "docs_upload_example_us_preset"); fetch(url, { method: "POST", body: formData }) .then((response) => { return response.text(); }) .then((data) => { document.getElementById("data").innerHTML += data; }); } });
위의 예제 코드는 form을 이용하여 여러 개의 파일을 업로드하는 코드이다.
반복문을 통해 files 배열의 각각의 요소(각각의 파일)들을 FormData 객체에
append
메서드로file
,upload_preset
각각의 프로퍼티의 값을 추가한다. 그리고fetch
를 이용하여 FormData 객체를 post 메서드의 body에 넣어 전송하고 그 결괏값인 response 값을 출력한다.
이제 위의 예제 코드를 참고해 내 프로젝트에 적용하면 된다.우선 나는 form 태그를 사용하지 않고, React-Dropzone에 이미지를 올리고, 업로드 버튼을 클릭하면 cloudinary에 이미지가 업로드되게끔 구현하려고 한다. React-Dropzone 사용 방법은 아래의 공식 문서를 확인하면 된다.
https://react-dropzone.js.org/
React-Dropzone을 사용하여 파일에 대한 드래그 앤 드롭 영역을 생성하였다.
const onDrop = useCallback(acceptedFiles => { setFiles( acceptedFiles.map(file => Object.assign(file, { preview: URL.createObjectURL(file), }) ) ); }, []); const { getRootProps, getInputProps } = useDropzone({ accept: { "image/*": [], }, onDrop, });
<div {...getRootProps()} className={styles.dropzone}> <input {...getInputProps()} /> <p>Drag 'n' drop some files here, or click to select files</p> </div> <aside className={styles.thumbsContainer}> { files.map(file => <Thumbs file={file} files={files} key={file.name} setFiles={setFiles} />) } </aside>
그리고 업로드 버튼을 클릭하면 이미지가 cloudinary에 업로드가 되도록 할 것이므로 버튼 태그에
handleUpload
함수가 호출되게 연결해 준다.<button className={styles.btn} onClick={handleUpload}> Upload </button>
버튼 클릭 시 호출 될
handleUpload
함수를 아래와 같이 작성해 주었다.const handleUpload = async () => { setLoading(true); const url = `https://api.cloudinary.com/v1_1/${process.env.REACT_APP_CLOUDINARY_NAME}/image/upload`; const uploaders = files.map(async file => { const formData = new FormData(); const config = { header: { "content-type": "multipart/form-data", }, }; formData.append("file", file); formData.append("upload_preset", process.env.REACT_APP_UPLOAD_PRESET); try { const response = await axios.post(url, formData, config) const files = await response.data; setlocalFiles(prev => [...prev, files]) } catch (error) { console.error(error); } await axios.all(uploaders); setUploadCheck(true); setLoading(false); setTimeout(() => { setUploadCheck(false); }, 1000); setFiles([]); });
우선 API 관련 개인 키값들은 .env 파일로 별도 분리를 해주었고,
fetch
대신에axios
를 사용하여 post 요청을 했다.요청이 성공적으로 진행되면 이 데이터들을 cloudinary에 업로드 및 저장을 하고,
axios.all
을 이용하여 모든 이미지가 업로드가 되면 업로드가 되었다는 UI를 표출해 줄 것이다.그리고 cloudinary의 디렉토리에 저장된 이미지들을 가져와서 프론트단에서 사용하는 방식으로 구현하려고 했는데, cloudinary의 디렉토리에 접근하려면 보안상의 이유로 Admin API를 사용하여 인증을 받고 이용해야 하는 것 같다.
[참고]
https://support.cloudinary.com/hc/en-us/articles/202521082-Listing-all-assets-within-a-folder
굳이 인증받으며 복잡하게 구현하려는 목적은 아니었기에... 어떤 방식으로 풀어나갈지 고민하다가 cloudinary에 이미지를 업로드하고, 동적으로 생성된 cloudinary의 이미지 데이터 값을 로컬스토리지에 저장하는 방식으로 구현하였다.
axios 요청 후 성공적으로 진행되었을 때 response로 들어오는 data 값을 localFiles state에서 관리하도록 했다. 그래서 localFiles의 초기값으로 로컬스토리지에 저장된 값이 있으면 해당 값을
JSON.parse()
를 통해 변환하여 localFiles에 담아두고, 없을 경우엔 빈 배열로 셋팅되게끔 하였다.try { const response = await axios.post(url, formData, config) const files = await response.data; setlocalFiles(prev => [...prev, files]) }
const getBg = () => { const bgimgs = localStorage.getItem("bg"); return bgimgs ? JSON.parse(bgimgs) : []; }; const [localFiles, setlocalFiles] = useState(getBg);
그리고
useEffect
훅으로 localFiles가 변경되면 로컬스토리지에 있는 데이터를 셋팅해주면 된다.useEffect(() => { localStorage.setItem("bg", JSON.stringify(localFiles)); // Make sure to revoke the data uris to avoid memory leaks, will run on unmount return () => files.forEach(file => URL.revokeObjectURL(file.preview)); }, [files, localFiles]);
그러면 이미지 업로드 할 때마다 로컬 스토리지에 동적으로 생성된 이미지 경로값이 담기는 것을 확인할 수 있다.
그리고 cloudinary의 Media Library에 업로드한 이미지가 저장되어 있는 것을 확인할 수 있다.
이제 이 데이터들을 화면에 이쁘게 표출해주면 된다!
구현된 화면 👇👇
이제 이 업로드 기능을 모멘텀 프로젝트에 적용해 봐야겠다!
이미지 업로드 관련 전체 로직은 github에서 확인할 수 있다.
https://github.com/joeunhye/react-dropzone
GitHub - joeunhye/react-dropzone: Cloudinary를 연동하여 이미지 Dropzone 구현하기
Cloudinary를 연동하여 이미지 Dropzone 구현하기. Contribute to joeunhye/react-dropzone development by creating an account on GitHub.
github.com
728x90'토이 프로젝트' 카테고리의 다른 글
크롬 모멘텀(Momentum)에 이미지 업로드 기능 추가하기 (0) 2023.03.06 리액트로 크롬 모멘텀(Momentum) 구현하기 (0) 2023.01.29 댓글