TRACK1 리팩토링 이야기, 이미지 미리보기 커스텀 훅


커스텀 훅

리액트 공식문서 를 통해 어떻게 커스텀 훅을 잘 사용할지 고민하며 만들어 보았다.

컴포넌트 분리를 고민할 때, 한 컴포넌트에는 한 가지의 기능만을 가지도록 만든다는 것이 큰 원칙이었다.

커스텀 훅은 또 다른 함수(기능)을 따로 분리함으로써 컴포넌트를 어지럽히지 않고 기능들을 쌓아올라갈 수 있지 않을까 생각했다.

특히, 이미지 미리보기 부분은 내가 지금 개발하고 있는 프로듀서 프로필 수정 페이지 뿐만 아니라 회원가입, 보컬 프로필 수정, 트랙 게시글 올리기 등등 아주 많이 사용하기 때문에 재사용성에 있어서 꼭 꼭 필요했다.

이미지 업로드 UI

export default function ProducerImageEdit() {
 
 function getImageFile(e: React.ChangeEvent<HTMLInputElement>) {
    e.preventDefault();
    const file = e.target.files;
    console.log(file);
  }
  
  return (
    <>
      {/* 프로듀서 프로필 이미지 업로더 */}
      <ProfileImageContainer>
        <ProfileImage  />
        <ChangePhotoIcon />
        <FileInput type="file" onChange={getImageFile} />
      </ProfileImageContainer>
    </>
  );
}

우선 input 태그로 file을 입력 받는다. 그럼 profileImage가 img 태그인데 여기 src로 이미지를 미리보기로 띄울 것이다!

그 다음, onChange로 key 이벤트를 input을 통해 받을 때, file이 계속 바뀌어서 들어가도록 했고 확인을 위해 log를 찍어보았다.

미리보기 커스텀 훅 작성

import { useState } from "react";

export default function useShowImage(imageFile: File | Blob | undefined | null) {
  const [showImage, setShowImage] = useState<string>();

  if (imageFile) {
    const reader = new FileReader();
    
    reader.readAsDataURL(imageFile);
    
    reader.onload = (e) => {
      setShowImage(e.target?.result as string);
    };
  } 

  return showImage;
}

먼저, 사용하는 곳에서 이미지 파일을 받아올 것이다.

공식문서에서 팁: Hook에서 Hook으로 정보 전달하기 부분을 읽고 만들었다.

컴포넌트에서 input 된 파일을 useState로 담아 커스텀 훅을 불러올 때 보내줄 것이다.

어쨋든 그렇게 받은 파일을 FileReader라는 자바스크립트 web API를 이용하여 이미지 url을 뽑아낼 것이다.

  1. 객체 생성
  2. readAsDataURL, readAsText, readAsArrayBuffer 등의 메서드를 통해 파일을 읽는다.
  3. onload 함수 이용 -> load 이벤트 핸들러에서 e.target.result를 사용하여 파일의 내용을 얻을 수 있다.
  4. 얻어낸 이미지 url을 담아서 return한다.

훅을 통해 알아낸 이미지 url을 불러와서 사용하기

export default function ProducerImageEdit() {
  const [image, setImage] = useState<File | Blob | undefined | null>(undefined);
  const showImage = useImagePreview(image);

  function getImageFile(e: React.ChangeEvent<HTMLInputElement>) {
    e.preventDefault();
    const file = e.target.files?.[0];
    setImage(file);
  }

  return (
    <>
      {/* 프로듀서 프로필 이미지 업로더 */}
      <ProfileImageContainer>
        {/* 사용자가 넣은 이미지 or 기본 사람 이미지 */}
        {/* {isImageUploaded ? <ProfileImage src={String(showImage)} /> : <ProfileImage src={String(profileImage)} />} */}
        <ProfileImage src={String(showImage)} />
        <ChangePhotoIcon />
        <FileInput type="file" onChange={getImageFile} />
      </ProfileImageContainer>
    </>
  );
}