React | 리액트 파일 input 다루기 1탄 - 버튼 클릭으로 파일 선택창 열기, FileLeader로 이미지&오디오 데이터 가져오기

dayannne·2024년 3월 31일
0

React

목록 보기
10/13
post-thumbnail

1탄 2탄에 나누어 리액트에서 첨부파일을 가져와 화면에 보여주고, 가져온 첨부파일을 API로 요청하는 방법에 대한 글을 써보려 한다.

이번 글에서는 버튼 클릭으로 input 파일 선택창을 실행하는 방법과, fileChangeHandler에서 FileLeader를 사용해서 이미지, 오디오 데이터 정보를 가져오는 방법에 대한 내용을 먼저 다루고,

그 다음글에서 react query의 useMutaion으로 이미지/오디오 파일 업로드 API 구현 방법과 실무에서 formData로 API 요청 시의 다양한 경우(json Data 함께 넣기, 여러개 파일 formData에 넣기)대처 방법에 대해 다뤄볼 예정이다.


1) 버튼 클릭으로 input 파일 선택창 실행하기

사용자가 버튼을 클릭하면 파일 선택 창을 열어 이미지 파일을 선택할 수 있게 하는 기능은 다음과 같이 구현할 수 있다.

const ref = useRef<HTMLInputElement>(null);

const fileHandler = () => {
  if (ref.current) {
    ref.current!.click();
  }
};

const handleFileChange = () =>{
 //...
}

 <button onClick={fileHandler}>
   <input
     name={name}
     type="file"
     accept="image/*"
     ref={ref}
     onChange={handleFileChange}
     className="a11y-hidden"
   />
<button>

💡 <!> 단언 연산자를 사용한 이유?

체이닝에서 ref.current!.click()과 같이 !를 사용해 주면 ref.current가 존재한다, 즉 Nullish(null이나 undefined)한 값이 아니라는 확신을 심어줄 수 있다.
그렇지 않을 시 타입 에러가 발생할 수 있다

1. useRef Hook 사용

const ref = useRef<HTMLInputElement>(null);

useRef는 React의 Hook 중 하나로, DOM 요소에 직접적으로 접근할 수 있게 해주는 친구로, 여기서는 <input type="file"> 엘리먼트에 접근하기 위해 사용된다.
타입 스크립트에서는 useRef<HTMLInputElement>(null)과 같이 초기값은 null로 지정해줄 것.

2. fileHandler 함수

버튼 클릭 이벤트 함수 안에서 ref.current가 존재할 경우, 즉 input 엘리먼트가 마운트된 상태라면 해당 엘리먼트의 click() 메서드를 호출해 준다.
그래서 사용자가 직접 input을 클릭한 것처럼 버튼 클릭 시 파일 선택창을 열게 한다.

const fileHandler = () => {
  if (ref.current) {
    ref.current.click();
  }
};

3. button과 input 요소

<button onClick={fileHandler}>
  <input
    name={name}
    type="file"
    accept="image/*"
    ref={ref}
    onChange={handleImageChange}
    className="a11y-hidden"
    multiple
  />
</ button>

button

  • onClick={fileHandler} : 버튼 클릭 시 fileHandler 함수를 실행하여 파일 선택 창을 연다.

input

  • ref : ref={ref}를 통해 이 input에 대한 참조를 저장
  • onChange={handleImageChange} : 파일이 선택되었을 때 실행될 콜백 함수
  • className="a11y-hidden" : input을 화면상에서 숨기기 위해 미리 css에서 설정한 클래스 가져오기
  • multiple : 한 번에 여러 개의 파일을 가져올 때 사용, 그렇지 않을 때에는 생략

2) FileLeader로 데이터 정보 가져오기 - 이미지, 오디오

이미지 데이터를 미리 화면에 띄워 보여주거나, 오디오 데이터의 이름, 실행 시간 등의 값을 가져올 때 FileReader를 활용할 수 있다.
먼저 input 파일 선택창 실행 후 받아온 파일을 다루기 위해 위 코드에서 handleFileChange에 해당하는 onChange 이벤트 함수를 세팅해 주자.

  • 한 개의 파일
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (file) {
      console.log("선택된 파일:", file);
}
  • 여러 개의 파일
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
    if (files) {
      for(const file of files){
		console.log("선택된 파일:", file);
      }
}

잠깐 💡 트러블 슈팅! - 동일 파일 연속으로 안가져와지는 오류

파일 선택창에서 한 번 가져온 파일이 연속적으로 불러와지지 않는 오류가 있다.
구글링하다 발견한 아주 간단한 방법은,

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    event.target.value = ""; // 동일 파일 업로드 오류 방지
    if (file) {
      console.log("선택된 파일:", file);
}

onChange 시 e.target.value에 담긴 값이 유지되어서 같은 파일 재업로드가 막히는 것 같다.
event.target.value값을 초기화해 주면 해결!

이미지 데이터

이미지를 미리 보여줄 수 있는 로직은 아래와 같이 구현해 준다.

const [selectedImage, setSelectedImage] = useState("");
const [imageFile, setImageFile] = useState<File>()

const handleImageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (file) {
      // FileReader를 사용해 파일 읽기(JS의 내장기능)
      const fileReader = new FileReader();
      // blob 타입의 file을 url 형태로 만들기
      fileReader.readAsDataURL(file);
      // 읽기 작업이 완료되면, 결과물이 data로 들어오고 아래 함수가 실행된다
      fileReader.onload = (e) => {
      	if (typeof data.target?.result === "string") {
            // useState 초기값으로 string("")을 넣었기 때문에 꼭 타입을 지정해서 변경해 줄 것
          	setSelectedImage(e.target?.result as string) // 미리보기를 위한 임시 url 
            	setImageFile(file) // API로 보내기 위한 File 저장
      	}	
      }
   }
}

//...

return(
<button onClick={fileHandler}>
  <input
    name={name}
    type="file"
    accept="image/*"// image 파일만 받아올 수 있도록 설정
    ref={ref}
    onChange={handleImageChange}
    className="a11y-hidden"
  />
  <img src={selectedImage} alt="이미지" />
</ button>
)

핵심은 딱 이 세 단계를 기억할 것!
1. const fileReader = new FileReader();
2. fileReader.readAsDataURL(file);
3. fileReader.onload = (e) => {}

오디오 데이터

오디오 파일 이름과 재생 시간을 가져오는 로직이다.
파일 이름은 그냥 file.name으로 가져올 수 있지만, 재생 시간과 같은 오디오 정보를 가져올 때에는 오디오 객체가 필요하다.

  const [fileName, setFileName] = useState("");
  const [duration, setDuration] = useState(0)
  
  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (file) {
      console.log("선택된 파일:", file);

      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = (e) => {
        // 읽기 작업이 완료되면, 오디오 객체를 생성하고 메타데이터를 로드
        const audio = new Audio(e.target?.result as string);
        audio.onloadedmetadata = () => {
          // 메타데이터 로드가 완료되면, 파일 이름과 재생 시간을 가져오기
          // file 이름
          setFileName(file.name);
          // 추출된 오디오 재생 시간
          setDuration(audio.duration) // (2분 30초=)150와 같이 초 단위로 가져와 짐
         
        };
      };
    }

 //...
    
 return(
   <>
     <button onClick={fileHandler}>
        <input
          name={name}
          type="file"
          accept="audio/*" // audio 파일만 받아올 수 있도록 설정
          ref={ref}
          onChange={handleImageChange}
          className="a11y-hidden"
        />
     </ button>
     <p>{fileName}</p>
     <p>{duration}s</p>
   </>
 )
 

여기서의 포인트는
1) const audio = new Audio(e.target?.result as string); - 오디오 객체 생성
2) audio.onloadedmetadata = () => {} 안에서 오디오 데이터 가져오기!

profile
☁️

0개의 댓글