30개의 프로젝트로 배우는 프론트엔드 with React (6-3) 이미지갤러리

productuidev·2022년 10월 10일
0

FE Study

목록 보기
60/67
post-thumbnail
post-custom-banner

[fastcampus] 30개의 프로젝트로 배우는 프론트엔드 with React (6-3)

04) UI 구현 (커스텀)

App.tsx
App.css

  • 이미지 없음/있음 case
  • flex를 활용한 반응형웹 UI 구현
  • 코드 일부 수정

참고자료 & 더 알아볼 자료

05) 드래그 드롭 사용하여 UX 개선

+ 버튼에 이미지를 드래그 드롭 시 이미지가 자동 첨부되어 갤러리에 리스팅되도록 구현

react-dropzone

https://react-dropzone.js.org/

JS로 된 라이브러리를 TS 지원하도록 설치 (add @types/)
yarn add @types/react-dropzone
import {useDropzone} from 'react-dropzone'

기본 사용법

useCallback, useDropzone hook 활용 (import)
onDrop 함수를 통해서 파일을 수락
기본 코드를 활용해서 이미지 갤러리에 구현

import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'

function MyDropzone() {
  const onDrop = useCallback(acceptedFiles => {
    // Do something with the files
  }, [])
  const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop})

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      {
        isDragActive ?
          <p>Drop the files here ...</p> :
          <p>Drag 'n' drop some files here, or click to select files</p>
      }
    </div>
  )
}

활용

  • 위의 기본 사용법을 활용하여 onDrop 함수를 만들고 useCallbakc에 acceptedFiles를 prop으로 내려줌
  • plus--button에 getRootProps를 스프레드속성으로 넣어서 내려줌
  • input에도 getInputProps를 스프레드속성으로 넣어서 내려줌
  • 이미지 첨부 후 console을 통해 체크 (console.log(acceptedFiles)
  • plus--button의 onClick 이벤트와 input의 useRef는 삭제 (직접 event.target.value에 접근하지 않으므로)
  • 이미지 파일을 매개변수로 받게 될 때 onDrop의 함수에 input에 있던 onChange 이벤트를 넣어서 기능 구현
  • acceptedFiles가 존재할 때(length가 있을 때) 파일 변환이 되도록 진행
  • acceptedFiles를 돌면서(for문) 파일을 가져와서 new FileReader로 추가되는 방식
import React, { useState, useCallback } from 'react';
import './App.css';
import ImageBox from './components/ImageBox';
import { useDropzone } from 'react-dropzone'

function App() {
  const [imageList, setImageList] = useState<string[]>([]);

  const onDrop = useCallback((acceptedFiles: any) => {
    console.log(acceptedFiles)

    if(acceptedFiles.length){
      for(const file of acceptedFiles) {
        // const file = event.currentTarget.files[0];
      
        // console.log(file.name)

        const reader = new FileReader();

        reader.readAsDataURL(file)
        reader.onloadend = (event) => {
          setImageList(prev => [...prev, event.target?.result as string])
        }
      }
    }
  }, [])
  const {getRootProps, getInputProps} = useDropzone({onDrop})

  return (
    <div className="container">
        <div className={'gallery--box ' + (imageList.length > 0 && 'true')}>
          <input type="file"
            {...getInputProps()}
          />
          <div className="plus--btn"
            {...getRootProps()}
          >
            +
          </div>

          { imageList.length === 0 &&
            <div className="no--case">
              <span className="title">이미지가 없습니다.</span><br />
              <span className="text">이미지를 추가해주세요.</span>
            </div>
          }
        </div>
        {
          imageList.map((el, idx)=><ImageBox key={el + idx} src={el} alt="" />)
        }
    </div>
  );
}

export default App;

결과

06) 추가된 이미지 삭제하기

  • 추가된 이미지에 - 버튼(삭제버튼)을 만들어서 삭제 버튼 클릭 시 갤러리에서 이미지가 삭제되는 removeFile 추가
  • 삭제하기 전 정말 삭제할 것인지 묻기(window.confirm)
  • splice 함수를 통해 이미지 갤러리 배열의 기존 요소를 삭제하여 배열의 내용을 변경
  • 이미지 추가 시 gallery--list 안에 ImageBox와 삭제 버튼(minus--btn)을 담고 onClick 이벤트로 removeFile 실행

참고자료

App.tsx

import React, { useState, useCallback } from 'react';
import './App.css';
import ImageBox from './components/ImageBox';
import { useDropzone } from 'react-dropzone'

function App() {
  const [imageList, setImageList] = useState<string[]>([]);
  
  ...
  
  const removeFile = (file: any) => {
    if (window.confirm("정말 삭제하시겠습니까?")) {
      const newFiles = [...imageList]
      newFiles.splice(newFiles.indexOf(file.name), 1)
      setImageList(newFiles)
    }
  }
  
  return (
    <div className="container">
        <div className={'gallery--box ' + (imageList.length > 0 && 'true')}>
          <input type="file" {...getInputProps()} />
          <div className="plus--btn" {...getRootProps()}>+</div>
          { imageList.length === 0 &&
            <div className="no--case">
              <span className="title">이미지가 없습니다.</span><br />
              <span className="text">이미지를 추가해주세요.</span>
            </div>
          }
        </div>
        {
          imageList.map((el, idx)=>
            <div key={el + idx} className="gallery--list">
              <ImageBox src={el} alt="" />
              <div className="minus--btn" onClick={()=>removeFile(el)}>-</div>
            </div>
          )
        }
    </div>
  );
  
 }

결과


중간에 코드 바꿈 + 강의에 없는 내용(삭제) 추가 등으로 조금 내용이 뒤죽박죽.. (드래그될때 탐색기가 안보여서 드래그과정이 안 보이게 캡처되서 아쉽)

profile
필요한 내용을 공부하고 저장합니다.
post-custom-banner

0개의 댓글