React - 파일 업로드(Drag & Drop)

sarang_daddy·2023년 12월 30일
1

React

목록 보기
26/26
post-thumbnail

웹에 파일을 업로드할 때 Drag & Drop 기능을 제공한다면 사용자 경험을 향상시킬 수 있다.
React에서 Drag & Drop을 이용한 파일 업로드하는 방법을 구현해 보자.

1. 부모 컴포넌트에서의 사용

  • 여러 컴포넌트에서 재사용이 가능하도록 구현해준다.
  • 부모 컴포넌트로부터 파일 선택 핸들러를 받아온다.
// 부모 컴포넌트
export default function AdVideoUploadContainer() {
  // 선택된 파일을 관리한다.
  const [file, setFile] = useState<File | null>(null);
  
  // 구현할 InputDragDrop에서 파일이 선택될 때 상태를 업데이트 한다.
  const handleFileSelect = (file: File | null) => {
    setFile(file);
  };  
  
  // 파일 업로드를 처리하는 로직
  const handleUpload = () => {
    if (file) {
      // Drag & Drop으로 가져온 파일 처리 로직 (API 호출 등)
    }
  };
  
  // ...
  
return 
 // ...
 <InputDragDrop
   onChangeFile={handleFileSelect}
   description="등록하실 비디오를 선택하거나 올려주세요."
 ></InputDragDrop>
 // ...

2. 컴포넌트 생성

  • Drag & Drop 이벤트에는 Enter, Leave, Over, Drop 4개의 이벤트가 필요하다.
  • 위 이벤트들은 이벤트 기본 동작과 이벤트 버블링 효과를 차단한다.
interface DragDropProps {
  onChangeFile: (file: File | null) => void;
  description?: string;
}

const DragDrop = ({
  onChangeFile,
  description = "파일 첨부",
}: DragDropProps) => {
  
  // 사용자가 파일을 드래드 중임을 상태로 관리 UI 변경을 위해 사용
  const [dragOver, setDragOver] = useState<boolean>(false);

  // 드래그 중인 요소가 목표 지점 진입할때
  const handleDragEnter = (e: React.DragEvent<HTMLLabelElement>) => {
    e.preventDefault();
    e.stopPropagation();
    setDragOver(true);
  };

  // 드래그 중인 요소가 목표 지점을 벗어날때
  const handleDragLeave = (e: React.DragEvent<HTMLLabelElement>) => {
    e.preventDefault();
    e.stopPropagation();
    setDragOver(false);
  };

  // 드래그 중인 요소가 목표 지점에 위치할때
  const handleDragOver = (e: React.DragEvent<HTMLLabelElement>) => {
    e.preventDefault();
    e.stopPropagation();
  };

  // 드래그 중인 요소가 목표 지점에서 드롭될때
  const handleDrop = (e: React.DragEvent<HTMLLabelElement>) => {
    e.preventDefault();
    e.stopPropagation();
    setDragOver(false);

    // 드래그되는 데이터 정보와 메서드를 제공하는 dataTransfer 객체 사용
    if (e.dataTransfer) {
      const file = e.dataTransfer.files[0];
      onChangeFile(file);
    }
  };
  
  // Drag & Drop이 아닌 클릭 이벤트로 업로드되는 기능도 추가
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files ? e.target.files[0] : null;
    onChangeFile(file);
    
    // input 요소의 값 초기화
    e.target.value = "";
  };

  return (
    <div className="flex flex-col justify-center items-center w-full">
      <div className="w-3/4">
        <Label
          className={`w-full flex-col gap-3 h-32 border-2 ${
            dragOver
              ? "border-blue-500 bg-blue-100 text-blue-500 font-semibold"
              : "border-gray-300"
          } rounded-md flex items-center justify-center cursor-pointer`}
          htmlFor="fileUpload"
          // Label에 드래그 앤 드랍 이벤트 추가
          onDragEnter={handleDragEnter}
          onDragLeave={handleDragLeave}
          onDragOver={handleDragOver}
          onDrop={handleDrop}
        >
          {description}
          <div className="w-9 h-9 pointer-events-none">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 24 24"
              strokeWidth={1}
              stroke="currentColor"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                d="M12 9v6m3-3H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
              />
            </svg>
          </div>
        </Label>
        <Input
          id="fileUpload"
          type="file"
          className="hidden"
          onChange={handleChange}
        ></Input>
      </div>
    </div>
  );
};


export default DragDrop;

3. 파일 확장자 제한과 알림 기능 추가하기

  • 업로드 파일의 확장자를 제한하는 기능을 추가해보자.
  • 허용되지 않은 확장자의 경우 알림을 통해 사용자에게 정보를 제공해보자.
// InputDragDrop 컴포넌트
interface InputDragDropProps {
  onChangeFile: (file: File | null) => void;
  description?: string;
  validExtensions?: string[]; // 확장자 정보를 받아온다.
}

const InputDragDrop = ({
  onChangeFile,
  description = "파일 첨부",
  validExtensions = ["*"], // 디폴트는 모든 확장자 허용.
}: InputDragDropProps) => {
  
  const { toast } = useToast(); // 알림 컴포넌트

  // ... 기존 코드
  
  // 파일 확장자 검증 함수
  const isValidExtension = (file: File) => {
    const fileName = file.name;
    const fileNameSplit = fileName.split(".");
    const fileExtension = fileNameSplit[fileNameSplit.length - 1];
    return validExtensions.includes(fileExtension);
  };

  // 허용된 확장자라면 업로드, 아니라면 사용자에게 알림
  const handleFileChange = (file: File | null) => {
    if (file && isValidExtension(file)) {
      onChangeFile(file);
    } else {
      toast({
        title: "잘못된 파일 형식",
        description: `지원하지 않는 파일 형식입니다. (${validExtensions.join(
          ", "
        )})로 등록해주세요.`,
        className: "bg-red-500 text-white",
      });
      onChangeFile(null);
    }
  };
  
  
  // ...기존 코드
  • 부모 컴포넌트에서는 props로 허용 확장자만 알려주면 된다.
// 부모 컴포넌트
return 
 // ...
 <InputDragDrop
   onChangeFile={handleFileSelect}
   description="등록하실 비디오를 선택하거나 올려주세요."
   validExtensions={["mp4"]} // 확장자 정보 추가
 ></InputDragDrop>
 // ...

profile
한 발자국, 한 걸음 느리더라도 하루하루 발전하는 삶을 살자.

0개의 댓글