React 부트캠프 TIL 18

정다롱·2024년 8월 30일

내일배움캠프 TIL

목록 보기
17/39

🖥️ 리액트로 이미지 업로드하고 미리보기 구현하기


여러 사이트에서 볼 수 있는 이미지 등록 기능!
보통 프로필 편집 페이지에서 가장 많이 사용되는 듯 하다.
전체 코드는 가장 밑에 있다.

이번에 진행하는 팀 프로젝트는 뉴스피드 사이트 만들기 인데, 우리 조는 요리 레시피를 공유하는 컨셉의 사이트를 만들기로 했다. 나는 그 중에서도 게시글 작성 파트를 담당하게 되어서! 요리 사진을 올리는 부분에 미리보기를 넣었다.


일단 코드 먼저

<ImageBox>
     <UploadButton>이미지 업로드</UploadButton>
        <input
          type="file"
          accept="image/*"
          style={{ display: "none" }}
        />
     <MinText>1:1 비율의 사진이 예쁘게 나와요❤️</MinText>
 </ImageBox>

처음에 작성했던 코드는 이랬다. 여기저기 검색해봤는데, input을 숨긴 다음 div나 버튼에 useRef로 인풋을 연결하는? 형태의 코드를 가장 많이 사용하는 것 같아서 일단 display:none; 으로 인풋을 숨겨놓고 버튼을 세팅했다.

그 다음 미리보기 기능을 만들기 전에 먼저 버튼과 input을 연결했다.

	const fileInputRef = useRef(null);
	const handleUploadButtonClick = () => {
    fileInputRef.current.click();
  };
  
  return 아래 연결
  
  	<ImageBox onClick={handleUploadButtonClick}>
   	<input
		type="file"
		accept="image/*"
		ref={fileInputRef}
		style={{ display: "none" }}
	/>

참고로 useRef는 담고 있는 값이 변경되어도 리랜더링 되지 않는다. 반대로 리랜더링 되어도 기존에 담고 있던 값이 변경되지는 않는다.

// useRef는 current 라는 프로퍼티를 기본적으로 갖고 있다.

const fileInputRef = useRef(null);
	= { current : null } 형태
ref={fileInputRef}
	= { current : <input> }

이렇게 요소가 로딩될 때 참조값을 변경해주어도 이것 때문에 리랜더링 되거나 하지 않는다는 것이다. 마찬가지로 연결해놓은 input은 페이지가 리랜더링 될 때에도 변경되지 않는다.


여기까진 순조로웠다...
미리보기 기능을 구현할 때 사용자가 등록한 이미지를 URL 형태로 바꾸어서 그걸 img src로 보여줘야 하는데 이게 검색을 해서 코드를 30분 동안 들여보고 있어도 이해가 잘 안 됐다. 완전 처음보는 게 나와서🥹

	const reader = new FileReader();

바로 요 FileReader 라는 자식이다.

FileReader 객체는 웹 애플리케이션이 비동기적으로 데이터를 읽기 위하여 읽을 파일을 가리키는File 혹은 Blob 객체를 이용해 파일의 내용을(혹은 raw data버퍼로) 읽고 사용자의 컴퓨터에 저장하는 것을 가능하게 해줍니다.

미리보기 코드를 보자.

  const [imageSrc, setImageSrc] = useState(null);
  const handleImageUpload = (event) => {
  
    const file = event.target.files[0];
    if (file) {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onloadend = () => {
        setImageSrc(reader.result);
      };
    }
  }
  
  <ImageBox onClick={handleUploadButtonClick}>
        {imageSrc
        	? (<ImagePreview src={imageSrc} alt="미리보기 이미지" />) 
        	: (<UploadButton>이미지 업로드</UploadButton>)}
        <input
          type="file"
          accept="image/*"
          ref={fileInputRef}
          style={{ display: "none" }}
          onChange={handleImageUpload}
        />
        <MinText>1:1 비율의 사진이 예쁘게 나와요❤️</MinText>
      </ImageBox>

위에서부터 한 줄씩 설명하자면

  • const [imageSrc, setImageSrc] = useState(null);
    - 이미지 주소를 저장하는 state
    - 사용자가 이미지를 재업로드 하는 경우에 미리보기 이미지가 변경된다.
    - return 문에서 이미지 div를 생성하는 조건이기도 함.

  • const handleImageUpload = (event) =>
    - 파일을 읽고 url을 state에 저장하는 함수

  • const file = event.target.files[0];
    - 사용자가 업로드한 files 배열의 0번 인덱스 항목 (한 장만 올리는 거라 0번 인덱스 그냥 쓰면 됨)

  • if (file)
    - file 이 존재한다면 = 사용자가 선택한 이미지가 정상적으로 올라왔다면

  • const reader = new FileReader();
    - FileReader 객체 생성

  • reader.readAsDataURL(file);
    - Starts reading the contents of the specified Blob, once finished, the result attribute contains a data: URL representing the file's data.
    - 지정한 데이터를 읽고 이를 URL 형태로 반환하는 메소드.

  • reader.onloadend = () =>
    - 이 이벤트는 읽기 동작이 성공적으로 완료 되었을 때마다 발생합니다.
    - readAsDataURL 메소드로 파일 URL을 로딩하면 실행되는 함수.

  • setImageSrc(reader.result);
    - FileReader.result : 파일의 컨텐츠. (읽기 작업이 완료되고 읽기 작업의 초기화에 사용한 방식으로 결정된 데이터의 포맷이 정해진 후에 유효합니다.)
    - readAsDataURL 에서 반환받은 URL을 state로 전달.

  • imageSrc ? ImagePreview : UploadButton
    - imageSrc 가 존재하는 경우 src = imageSrc 인 미리보기 div를 보여준다.
    - imageSrc 가 존재하지 않으면 기존에 있던 업로드 버튼을 보여준다.



전체 코드

const MainImage = () => {
  const [imageSrc, setImageSrc] = useState(null);
  const fileInputRef = useRef(null);

  const handleImageUpload = (event) => {
    const file = event.target.files[0];
    if (file) {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onloadend = () => {
        setImageSrc(reader.result);
      };
    }
  };

  const handleUploadButtonClick = () => {
    fileInputRef.current.click();
  };

  return (
    <>
      <ImageBox onClick={handleUploadButtonClick}>
        {imageSrc ? (
          <ImagePreview src={imageSrc} alt="미리보기 이미지" />
        ) : (
          <UploadButton>이미지 업로드</UploadButton>
        )}
        <input
          type="file"
          accept="image/*"
          ref={fileInputRef}
          style={{ display: "none" }}
          onChange={handleImageUpload}
        />
        <MinText>1:1 비율의 사진이 예쁘게 나와요❤️</MinText>
      </ImageBox>
    </>
  );
};

0개의 댓글