type이 file인 <input>의 파일 명 가져오기 & 이미지 미리보기 구현하기! (feat. 커스터마이징)

💛 nalsae·2024년 2월 13일
2

🔥 트러블 슈팅

목록 보기
6/7
post-thumbnail

 얼마 전 사내 프로젝트를 개발하던 중에 typefile<input> 요소를 다룰 일이 있었다. <input> 요소의 typefile로 지정하게 되면 아래와 같이 사용자가 파일을 등록할 수 있는 버튼이 생긴다.

 사용자는 파일 선택 버튼을 클릭하여 로컬 환경에서 원하는 파일을 선택하여 업로드할 수 있다. 만약 이 파일을 서버에 저장해야 한다면, 사용자가 등록한 파일을 Blob 객체로 가공한 후 request body에 담아 서버로 전달하면 될 것이다. 그러나 이번 사내 프로젝트에서는 파일을 서버에 저장할 필요까지는 없고, 클라이언트 단에서 등록한 파일의 데이터만 참조하여 저장하는 작업이 필요했다. 구현해야 하는 기능을 요약하면 다음과 같다.

  1. 등록한 이미지 파일의 이름을 가져오기
  2. 등록한 이미지 파일의 미리보기

📌 이미지 파일의 이름을 가져오자!

 우선 등록한 이미지 파일의 이름을 가져오는 것은 굉장히 쉽다. 바로 Change 이벤트의 타깃이 가지고 있는 files 프로퍼티를 참조하면 되기 때문이다. console.log 메서드를 호출하여 확인해보면 다음과 같다.

// onChange 이벤트 핸들러의 콜백 함수
const handleChangeFile = (event: React.ChangeEvent<HTMLInputElement>) => {
 if (event.currentTarget.files) {
   // ⭐️ Change 이벤트 타깃의 files 프로퍼티 ⭐️
   const file = event.currentTarget.files[0];

   console.log(file);
 };
};

 위와 같이 files 배열 안에는 수정한 시간, 수정한 날짜, 이름, 파일 크기, 파일 타입 등을 프로퍼티로 가지는 File 객체가 들어있다. 여기서 추가적으로 파일 타입을 나타내는 type 프로퍼티로 유효성 검사를 진행할 수도 있겠지만, 구현해야 할 기능은 등록한 이미지 파일의 이름만 가져오는 것이었으니 File 객체의 name 프로퍼티만 참조하면 만사 해결이다!


📌 이미지 파일의 미리보기를 구현해보자!

 다음으로 등록한 이미지 파일의 미리보기를 구현해야 한다. 이는 FileReader API를 사용하면 쉽게 구현할 수 있다. MDN에 FileReader를 검색하면 활용할 수 있는 다양한 프로퍼티와 메서드를 볼 수 있다. 이 글에서는 그 중에서도 result 프로퍼티와 readAsDataURL, onloadend 메서드에 주목해보고자 한다.

 먼저 result 프로퍼티를 살펴보자. MDN에서 살펴본 result 프로퍼티의 설명은 다음과 같다.

FileReader.result

파일의 내용을 반환합니다. 이 속성은 읽기가 완료 된 후에만 사용 가능 하며 데이터의 형식은 읽기 작업에 어떤 함수가 사용되었는가에 의해 정해집니다.

값은 읽기 작업에 사용된 함수에 의해 string 또는 ArrayBuffer 가 됩니다.

 여기서 주목해야 할 부분은 마지막 줄이다. 읽기 작업에 사용되는 함수에 바로 앞서 언급했던 readAsDataURL가 해당되기 때문이다. MDN에 명시되어 있는 읽기 작업에 사용되는 함수는 지원이 종료된 readAsBinaryString을 제외하고 readAsArrayBuffer, readAsDataURL, readAsText로 총 3가지가 있다. 만약 파일을 읽는 과정에서 readAsArrayBuffer를 호출하면 값으로 ArrayBuffer 형식을, readAsDataURL이나 readAsText를 호출하면 값으로 string 형식을 가지게 된다.

 readAsDataURL 메서드로 사용자가 입력한 파일을 읽으면 result 프로퍼티의 값 데이터 형식이 string이 되는 것은 알겠다. 그렇다면 result에는 어떤 데이터가 들어가게 되는 것일까? MDN을 참조하면 다음과 같다.

FileReader.readAsDataURL()

readAsDataURL 메서드는 컨텐츠를 특정 Blob 이나 File에서 읽어 오는 역할을 합니다. 읽어오는 read 행위가 종료되는 경우에, readyState의 상태가 DONE이 되며, loadend 이벤트가 트리거 됩니다. 이와 함께, base64 인코딩 된 스트링 데이터result 속성(attribute)에 담아지게 됩니다.

 글자 그대로다. readAsDataURL 메서드는 파일을 읽은 후 onloadend 메서드를 호출함과 동시에, 읽은 파일의 데이터를 base64 방식으로 인코딩하여 result 프로퍼티의 값으로 저장하는 것이다. 실제로 console.log 메서드를 호출하여 읽기 작업이 완료된 FileReader 객체를 확인해보면 다음과 같다.

// onChange 이벤트 핸들러의 콜백 함수
const file = event.currentTarget.files[0];

const reader = new FileReader();
// ⭐️ 파일 읽는 작업 시작 ⭐️
reader.readAsDataURL(file);
// 파일 읽는 작업 완료 후 호출될 onloadend 메서드 정의
reader.onloadend = () => {
 console.log(reader);
};

 위와 같이 result 프로퍼티의 값에 입력한 이미지 파일의 데이터가 base64 방식으로 인코딩된 문자열로 저장된 것을 확인할 수 있다. 그리고 이 문자열을 <image> 요소의 src 어트리뷰트 값으로 지정하면 입력한 이미지 파일의 미리보기를 구현할 수 있다.


📝 예제로 살펴보기

 지금까지 살펴본 바를 토대로 기능을 구현한 코드와 예제를 첨부하자면 다음과 같다. 필자는 useInputFile이라는 커스텀 훅을 생성하여 Change 이벤트와 관련된 로직을 분리하고자 했다.

// useInputFile.ts
import { useRef, useState } from "react";

export const useInputFile = () => {
  const imageRef = useRef<HTMLImageElement>(null);

  const [fileName, setFileName] = useState("");
  const [isLoaded, setIsLoaded] = useState(false);

  const handleChangeFile = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.currentTarget.files) {
      const file = event.currentTarget.files[0];

      // FileReader 객체 생성
      const reader = new FileReader();
      // 파일 읽는 작업 시작
      reader.readAsDataURL(file);
      // 파일 읽는 작업 완료 후 호출될 onloadend 메서드 정의
      reader.onloadend = () => {
        if (!imageRef.current) return;

        // ⭐️ 파일 이름 가져와서 상태로 저장 ⭐️
        setFileName(file.name);
        // ⭐️ <image> 요소의 src 값 지정 ⭐️
        imageRef.current.src = reader.result as string;
      };

      setIsLoaded(true);
    }
  };

  return { imageRef, fileName, isLoaded, handleChangeFile };
};

🎀 <input> 요소 커스터마이징하기

 마지막으로 한 가지만 더 살펴보자. 상단 구현 예제를 살펴보면 알 수 있듯이 추가적으로 요소에 커스터마이징을 한 상태이다. <input> 요소를 커스터마이징하는 데는 다양한 방법이 있겠지만, 필자는 <input> 요소에 sr-only 클래스를 지정하여 접근성을 해치지 않는 선에서 숨기고 <label> 요소를 <button>처럼 활용하는 방법을 선택했다. 적용한 CSS는 상단 예제의 styles.css 파일에 기재되어 있다.


😊 알고 나면 쉬운 FileReader API

 여기까지 File 객체와 FileReader API를 활용하여 사용자가 입력한 이미지 파일의 이름을 가져오는 기능과 미리보기를 구현해보았다. 사실 이전에도 프로젝트를 진행하면서 FileReader API를 활용해본 적은 있었는데, API에 내장된 프로퍼티와 메서드를 제대로 톺아보는 건 이번이 처음이라 흥미로운 경험이었다. 한 번 제대로 정리해두었으니 앞으로는 잘 까먹지 않을 것 같다 ㅎㅎ

🙏 출처

https://developer.mozilla.org/en-US/docs/Web/API/FileReader

profile
𝙸'𝚖 𝚊 𝚍𝚎𝚟𝚎𝚕𝚘𝚙𝚎𝚛 𝚝𝚛𝚢𝚒𝚗𝚐 𝚝𝚘 𝚜𝚝𝚞𝚍𝚢 𝚊𝚕𝚠𝚊𝚢𝚜. 🤔

0개의 댓글