본문 바로가기
Frontend/React

React 파일 업로드, 드래그 앤 드랍, 미리보기 기능 구현하기

by 신림쥐 2025. 2. 28.
728x90

 

     


     

    file 요소란?

    file 유형의 <input> 요소에는 저장 장치의 파일을 하나 혹은 여러 개 선택할 수 있습니다. 그 후, 양식을 제출해 서버로 전송하거나, File API를 사용한 JavaScript 코드로 조작할 수 있습니다.

     

    <input type="file">

     

    <input type="file"> - HTML: Hypertext Markup Language | MDN

    file 유형의 <input> 요소에는 저장 장치의 파일을 하나 혹은 여러 개 선택할 수 있습니다. 그 후, 양식을 제출해 서버로 전송하거나, File API를 사용한 JavaScript 코드로 조작할 수 있습니다.

    developer.mozilla.org

     

     

     

    파일 업로드 기능 구현 하기

    1. 파일 요소 추가

    - 가장 기본 파일 업로드 요소 추가

    <input type="file">

     

     

    2. 파일 스타일 적용

    - Label 사용

    1. Label 요소를 추가해준다. label의 htmlFor과 input의 id는 동일해야 한다.

    <label htmlFor="avatar">파일 선택</label>
    <input type="file" id="avatar" />

    2. css로 <input type="file"> 태그를 display: none 속성으로 숨김 처리하고 Label을 원하는 방식으로 커스텀 한다.

    label {
      width: 100px;
      padding: 10px;
      background-color: #e2e2e2;
    }
    #avatar {
       display: none 
    }

    3. 선택한 파일을 저장하는 state 변수를 생성한다음, onChange 핸들러 이벤트를 추가한다.

    import React, { useState } from 'react';
    
    const FileUploadWithLabel: React.FC = () => {
      const [selectedFile, setSelectedFile] = useState<File | null>(null);
    
      // 파일 선택 핸들러
      const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files ? e.target.files[0] : null;  // 선택한 파일
        setSelectedFile(file);
      };
    
      return (
        <div>
          <label htmlFor="avatar">
            파일 선택
          </label>
          <input type="file" id="avatar" onChange={handleFileChange} />
          
          <div>
            {selectedFile ? (
              <p>선택한 파일: {selectedFile.name}</p>
            ) : (
              <p>파일이 선택되지 않았습니다.</p>
            )}
          </div>
        </div>
      );
    };
    
    export default FileUploadWithLabel;

     

     

    - Button 과 onClick 사용

    1. 버튼 요소를 추가하고 onclick 핸들러를 추가한다.

    // 버튼 클릭 시 파일 선택 창 열기
    const handleUpload = () => {
     fileInputRef.current?.click();
    };
      
    <button type="button" onClick={handleUpload}>
     파일 선택
    </button>
    <input type="file" id="avatar" />

     

    2. css로 <input type="file"> 태그를 display: none 속성으로 숨김 처리하고 button을 원하는 방식으로 커스텀 한다.

    button {
      width: 100px;
      padding: 10px;
      background-color: #e2e2e2;
    }
    #avatar {
       display: none 
    }

     

    3. 선택한 파일을 저장하는 state 변수를 생성한다음, onChange 핸들러 이벤트를 추가한다.

    import React, { useState, useRef } from 'react';
    
    const FileUploadWithButton: React.FC = () => {
     const [selectedFile, setSelectedFile] = useState<File | null>(null);
      const fileInputRef = useRef<HTMLInputElement>(null);
    
      const handleUpload = () => {
        fileInputRef.current?.click();
      };
      
      // 파일 선택 핸들러
      const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files ? e.target.files[0] : null;  // 선택한 파일
        setSelectedFile(file);
    
        if(fileInputRef.current) {
          fileInputRef.current.value = '';
        }
      };
    
      return (
        <div>
          <button type="button" onClick={handleUpload}>
            파일 선택
          </button>
          <input type="file" id="avatar" ref={fileInputRef} onChange={handleFileChange} />
          
          <div>
            {selectedFile ? (
              <p>선택한 파일: {selectedFile.name}</p>
            ) : (
              <p>파일이 선택되지 않았습니다.</p>
            )}
          </div>
        </div>
      );
    };
    
    export default FileUploadWithButton;

     

    3. 드래그 앤 드랍 기능 적용

    - 나는 버튼으로 작업을 진행하겠다.

    • onDragOver : 드래그 이벤트
    • onDrop : 파일 드래그 후 드랍시 핸들러

     

    1. 드래그 앤 드랍의 기본 브라우저 이벤트를 취소하는 핸들러 추가

    // 드래그 앤 드롭 핸들러
    const handleDragOver = (e: React.DragEvent) => {
     e.preventDefault();
    };

     

    2. 드래그 이벤트 핸들러 추가

    const handleDrop = (e: React.DragEvent) => {
     e.preventDefault();
     const file = e.dataTransfer.files ? e.dataTransfer.files[0] : null;
     setSelectedFile(file);
    };

     

    3. html 태그에 드래그 영역 추가

    <div ref={dropzoneRef} onDragOver={handleDragOver} onDrop={handleDrop}
    style={{ border: '2px dashed gray', padding: '20px', marginTop: '10px', textAlign: 'center' }}
    >
    파일을 이곳에 드래그 앤 드롭하세요
    </div>

     

    전체 코드

    import React, { useState, useRef } from 'react';
    
    const FileUploadWithButton: React.FC = () => {
     const [selectedFile, setSelectedFile] = useState<File | null>(null);
      const fileInputRef = useRef<HTMLInputElement>(null);
      const dropzoneRef = useRef<HTMLDivElement>(null);
    
      const handleButtonClick = () => {
        fileInputRef.current?.click();
      };
      
      const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files ? e.target.files[0] : null;
        setSelectedFile(file);
      };
    
      const handleDrop = (e: React.DragEvent) => {
        e.preventDefault();
        const file = e.dataTransfer.files ? e.dataTransfer.files[0] : null;
        setSelectedFile(file);
      };
      
      const handleDragOver = (e: React.DragEvent) => {
        e.preventDefault();
      };
    
      return (
        <div>
          <button type="button" onClick={handleButtonClick}>
            파일 선택
          </button>
          <input type="file" id="avatar" ref={fileInputRef} onChange={handleFileChange} />
          
          <div ref={dropzoneRef} onDragOver={handleDragOver} onDrop={handleDrop}
          	style={{ border: '2px dashed gray', padding: '20px', marginTop: '10px', textAlign: 'center' }}
          >
           파일을 이곳에 드래그 앤 드롭하세요
          </div>
          
          <div>
            {selectedFile ? (
              <p>선택한 파일: {selectedFile.name}</p>
            ) : (
              <p>파일이 선택되지 않았습니다.</p>
            )}
          </div>
        </div>
      );
    };
    
    export default FileUploadWithButton;

     

     

    4. 미리보기 기능 적용

    - 이미지 경로를 저장하는 state 변수 추가

    const [preview, setPreview] = useState<string | null>(null);

     

    - 선택한 파일을 파일 타입으로 변환하여 preview 변수 저장하는 핸들러 추가

    const handlePreview = (file: any) => {
        if (file) {
          const reader = new FileReader();
          reader.onloadend = () => {
            setPreview(reader.result as string);
          };
          reader.readAsDataURL(file);
        } else {
          setPreview(null);
        }
      }

     

    - handleFileChange, handleDrop 핸들러에 handlePreview() 로직 추가

    const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files ? e.target.files[0] : null;
        setSelectedFile(file);
        handlePreview(file);
      };
    
      const handleDrop = (e: React.DragEvent) => {
        e.preventDefault();
        const file = e.dataTransfer.files ? e.dataTransfer.files[0] : null;
        setSelectedFile(file);
        handlePreview(file);
      };

     

    - html 태그에 미리보기 영역 추가

    {preview && <img src={preview} alt="미리보기" style={{ maxWidth: '200px', marginTop: '10px' }} />}

     

    전체 코드

    import React, { useState, useRef } from 'react';
    
    const FileUploadWithButton: React.FC = () => {
      const [selectedFile, setSelectedFile] = useState<File | null>(null);
      const [preview, setPreview] = useState<string | null>(null);
      const fileInputRef = useRef<HTMLInputElement>(null);
      const dropzoneRef = useRef<HTMLDivElement>(null);
    
      const handlePreview = (file: any) => {
        if (file) {
          const reader = new FileReader();
          reader.onloadend = () => {
            setPreview(reader.result as string);
          };
          reader.readAsDataURL(file);
        } else {
          setPreview(null);
        }
      }
    
      const handleButtonClick = () => {
        fileInputRef.current?.click();
      };
    
      const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files ? e.target.files[0] : null;
        setSelectedFile(file);
        handlePreview(file);
      };
    
      const handleDrop = (e: React.DragEvent) => {
        e.preventDefault();
        const file = e.dataTransfer.files ? e.dataTransfer.files[0] : null;
        setSelectedFile(file);
        handlePreview(file);
      };
    
      const handleDragOver = (e: React.DragEvent) => {
        e.preventDefault();
      };
    
      return (
        <div>
          <button type="button" onClick={handleButtonClick}>
            파일 선택
          </button>
          <input type="file" ref={fileInputRef} onChange={handleFileChange} />
          
          <div ref={dropzoneRef} onDragOver={handleDragOver} onDrop={handleDrop}
            style={{ border: '2px dashed gray', padding: '20px', marginTop: '10px', textAlign: 'center' }}
          >
            파일을 이곳에 드래그 앤 드롭하세요
          </div>
          
          <div>
            {selectedFile ? (
              <>
                <p>선택한 파일: {selectedFile.name}</p>
                {preview && <img src={preview} alt="미리보기" style={{ maxWidth: '200px', marginTop: '10px' }} />}
              </>
            ) : (
              <p>파일이 선택되지 않았습니다.</p>
            )}
          </div>
        </div>
      );

     

     

     

    728x90