[React] 사진 업로더 만들기 1

Chloé·2021년 4월 1일
1
post-custom-banner

업로드 한 사진 미리보기 / 삭제하기

1. 접근하기

  1. 사진 썸네일과 삭제 버튼이 들어있는 div 레이아웃을 생성한다.
  2. 추가한 사진을 배열로 저장한다.
    • 배열의 요소 하나는 등록될 사진 하나에 해당되며, 요소의 내용으로는 1) 해당 사진의 파일과 2) 파일의 URL을 가진다.
    • 배열에 사진을 저장할 때 미리보기할 URL을 함께 생성하여 저장한다.
    • 사진을 여러 번 나눠서 추가할 경우, 새롭게 추가하는 사진이 더 앞 순서로 생성되도록 한다(즉, 가장 최근에 추가한 파일부터 순서대로 보여준다).
  3. 배열을 div에 맵핑하여 보여준다.
    • 이 때는 url만 이용한다.
  4. 삭제 버튼을 누르면 배열에서 해당 사진을 삭제한다.

2. 배열 저장하기

새롭게 추가할 사진 배열 설정하기

  const [ photoToAddList, setPhotoToAddList ] = useState([]);
  • useState를 이용해 photoToAddList를 설정했다.
  • 이 때 해당 리스트의 각 요소는 이런 형태로 생성될 예정이다:
    { id: {사진 이름}, 
      file: {사진 파일}, 
      url: {사진 파일에 해당되는 URL}
    }
  • 이 배열은 1) 사진 미리보기 생성 2) DB로 사진 업로드 두 군데에 사용될 예정이다.

input 태그에 사진 핸들러 설정하기

          <input 
            type="file" 
            accept="image/jpg, image/jpeg, image/png" 
            multiple 
            ref={photoInput}
            onChange={(e) => handlePhoto(e)}
            style={{display: 'none'}} 
          />
  • input에 변화가 있을 경우 작동하는 handlePhoto를 설정했다.

사진 핸들러에서 배열 저장하기

  const handlePhoto = (e) => {
    const temp = []
    const photoToAdd = e.target.files;
    
    for (let i = 0; i < photoToAdd.length; i++) {
      temp.push({ id: photoToAdd[i].name, file: photoToAdd[i], url: URL.createObjectURL(photoToAdd[i]) })
    };
    setPhotoToAddList(temp.concat(photoToAddList))
  };
  • handlePhoto는 두 가지 역할을 한다:
    1. 새롭게 입력되는 여러 장의 사진들을 각각 하나의 요소로 설정해 배열에 넣어준다.
    2. 이 때, 사진의 URL 정보를 생성해 함께 등록한다.
  • URL 정보를 생성하기 위해 URL.createObjectURL를 이용했다.
  • for문 안에서 setPhotoToAddList를 설정하면, 가장 마지막 사진만 concat 된다.
    • 따라서 임시 배열(temp)을 설정하여, 한 번 등록할 때 등록된 여러 장의 사진들을 임시 배열에 담은 후, for문을 다 돌고 난 다음 해당 임시 배열을 원 배열에 concat 하는 방식을 택했다.
  • 사진을 여러 번 나눠서 등록할 때, 더 나중에 등록한 사진이 더 앞 순서에 나타나도록 하기 위해 photoToAddList.concat(temp) 대신 temp.concat(photoToAddList)로 작성했다.

3. 배열을 div에 맵핑하기

  const photoToAddPreview = () => {
    return photoToAddList.map((photo) => {
      return (        
        <div className="photoBox" key={photo.url}>
          <CloseCircleFilled className="photoBoxDelete"}/>
          <img className="photoPreview" src={photo.url} />
      </div>
      )
    })
  };
  • map을 이용해 photoToList의 각 요소들을 하나씩 꺼내 div에 담는다.
  • 이 때 map 안에서 한 번 더 return을 설정해야 한다.
    • 어떤 경우에는 map 내부에서 return을 설정해야 하고, 어떤 경우에는 설정하지 않아도 바로 렌더링 되는지 잘 모르겠다.
    • 예를 들어 photoToAddPreview를 함수형으로 설정하지 않고 바로 map으로 연결하는 경우 (const photoToAddPreview = photoToAddList.map(...)) 에는 따로 리턴값을 설정하지 않더라도 컴포넌트가 생성되는 것 같다.
    • 좀 더 공부해볼 것.
        { photoToAddPreview() }
  • 전체 컴포넌트의 return 부분은 이렇게 설정하면 된다.

4. 삭제하기 버튼 구현

  const onRemoveToAdd = (deleteUrl) => {
    setPhotoToAddList(photoToAddList.filter(photo => photo.url != deleteUrl))
  }
  • onRemove 함수는 url을 인자로 받는다.
    • 이 때 배열에서 해당 값을 뺀 나머지 값들만을 반환한 후,
    • 새 배열로 원래 배열을 대체해주면 된다.
<CloseCircleFilled 
 className="photoBoxDelete" 
  onClick={()=>onRemoveToAdd(photo.url)}
/>
  • 삭제하기 버튼에서 onClick시 해당 url을 onRemove에 넘겨주도록 설정한다.

5. 전체 코드

function PhotoUploader() {
  
  // 사진 업로드 버튼 이벤트 핸들러
  const photoInput = useRef();
  const handleClick = () => {
    photoInput.current.click();
  };

  // 사진 등록하기 및 미리보기 기능 구현 
  const [ photoToAddList, setPhotoToAddList ] = useState([]);
  
  const photoToAddPreview = () => {
    return photoToAddList.map((photo) => {
      return (        
        <div className="photoBox" key={photo.url}>
          <CloseCircleFilled className="photoBoxDelete" onClick={()=>onRemoveToAdd(photo.url)}/>
          <img className="photoPreview" src={photo.url} />
      </div>
      )
    })
  };
  
  const onRemoveToAdd = (deleteUrl) => {
    setPhotoToAddList(photoToAddList.filter(photo => photo.url != deleteUrl))
  }

  const handlePhoto = (e) => {
    const temp = []
    const photoToAdd = e.target.files;
    
    for (let i = 0; i < photoToAdd.length; i++) {
      temp.push({ id: photoToAdd[i].name, file: photoToAdd[i], url: URL.createObjectURL(photoToAdd[i]) })
    };
    setPhotoToAddList(temp.concat(photoToAddList))
  };
  return (
  <div className="contentWrapper">
    <div className="contentBody photoUploaderWrapper">
      <Row justify="end">
        <p>클로이와 홍대 나들이</p>
      </Row>
      <div className="photoUploaderContent">
        <div className="photoBox addPhoto">
          {/* <PlusOutlined /> */}
          <PictureFilled onClick={handleClick} />
          <input 
            type="file" 
            accept="image/jpg, image/jpeg, image/png" 
            multiple 
            ref={photoInput}
            onChange={(e) => handlePhoto(e)}
            style={{display: 'none'}} 
          />
        </div>
        { photoToAddPreview() }
        { photoAddedPreview() }
      </div>
    </div>
    <Row justify="center">
      <Button className="photoUploadComplete" onClick={ savePhoto }>기록하기</Button>
    </Row>
  </div>
  );
}

export default PhotoUploader;

이렇게! 삭제 버튼이 있는 사진 미리보기 완성!

profile
클로이 데일리 로그
post-custom-banner

0개의 댓글