React + Zod: 안전한 이미지 업로드 폼 만들기

Taesoo Kim·2025년 3월 31일

이미지 업로드 기능을 구현할 때, 사용자가 올바른 파일 형식을 선택했는지 검증하는 것이 중요합니다. 이 과정에서 zod를 활용하면 쉽고 직관적으로 유효성 검사를 수행할 수 있습니다. 이번 글에서는 zodreact-hook-form을 사용하여 안전한 이미지 업로드 폼을 만드는 방법을 다뤄보겠습니다.

1. Zod란 무엇인가?

zodTypeScript 기반의 스키마 선언 및 유효성 검사 라이브러리로, 직관적인 API를 통해 데이터를 검증할 수 있도록 도와줍니다. 특히 react-hook-form과 함께 사용하면 폼 입력 값을 간편하게 검증할 수 있습니다.

🔹 Zod의 주요 기능

  1. 스키마 선언: 객체 구조를 정의하고 타입을 강제할 수 있습니다.
  2. 유효성 검사: 정해진 조건을 만족하지 않으면 자동으로 에러 메시지를 반환합니다.
  3. TypeScript 지원: z.infer<typeof schema>를 사용해 타입을 자동으로 추론할 수 있습니다.

2. Zod를 활용한 유효성 검사 스키마 정의

이미지 업로드 폼에서 중요한 점은 파일이 선택되었는지올바른 이미지 형식인지를 검증하는 것입니다. 이를 위해 zodcustomrefine을 사용합니다.

import * as z from "zod";

const formSchema = z.object({
  image: z
    .custom<FileList>()
    .refine((files) => files?.length === 1, "이미지를 선택해주세요.")
    .refine(
      (files) => files?.[0]?.type.startsWith("image/"),
      "이미지 파일만 업로드 가능합니다."
    ),
});

✅ 위 코드에서 Zod가 하는 역할

  • custom<FileList>(): FileList 타입의 입력값을 검증할 준비를 합니다.
  • .refine((files) => files?.length === 1, "이미지를 선택해주세요.")
    • 사용자가 파일을 하나 이상 선택했는지 확인합니다.
  • .refine((files) => files?.[0]?.type.startsWith("image/"), "이미지 파일만 업로드 가능합니다.")
    • 파일의 MIME 타입이 image/로 시작하는지 검사하여 이미지 파일 여부를 확인합니다.

3. React Hook Form과 Zod 연결하기

이제 react-hook-form을 사용하여 zod 스키마를 resolver로 적용합니다. 이를 통해 폼 입력값이 자동으로 검증되며, 오류 발생 시 UI에서 쉽게 처리할 수 있습니다.

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

const form = useForm({
  resolver: zodResolver(formSchema),
});

📌 핵심 포인트

  • useFormresolverzodResolver(formSchema)를 전달하면 자동으로 검증이 이루어집니다.
  • 사용자가 잘못된 파일을 업로드하면 react-hook-form이 에러 상태를 감지하고 적절한 메시지를 표시할 수 있습니다.

4. 최종 코드

import { useState, useCallback } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { useDropzone } from "react-dropzone";
import { Button } from "@/components/ui/button";

const formSchema = z.object({
  image: z
    .custom<FileList>()
    .refine((files) => files?.length === 1, "이미지를 선택해주세요.")
    .refine(
      (files) => files?.[0]?.type.startsWith("image/"),
      "이미지 파일만 업로드 가능합니다."
    ),
});

export function ImageUploadForm() {
  const [preview, setPreview] = useState<string | null>(null);
  const form = useForm({ resolver: zodResolver(formSchema) });

  const onDrop = useCallback((acceptedFiles: File[]) => {
    if (acceptedFiles[0]) {
      const reader = new FileReader();
      reader.onloadend = () => setPreview(reader.result as string);
      reader.readAsDataURL(acceptedFiles[0]);

      const dataTransfer = new DataTransfer();
      dataTransfer.items.add(acceptedFiles[0]);
      form.setValue("image", dataTransfer.files);
    }
  }, [form]);

  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    onDrop,
    accept: { "image/*": [".jpg", ".jpeg", ".png", ".gif", ".webp"] },
    multiple: false,
  });

  return (
    <form onSubmit={form.handleSubmit(() => {})}>
      <div {...getRootProps()} className="border-2 border-dashed p-6 cursor-pointer">
        <input {...getInputProps()} />
        {isDragActive ? "이미지를 놓아주세요" : "이미지를 업로드하세요"}
      </div>
      {preview && <img src={preview} alt="Preview" className="mt-4 max-h-64" />}
      <Button type="submit">업로드</Button>
    </form>
  );
}

5. 마무리

이번 글에서는 zod를 활용하여 React에서 안전한 이미지 업로드 폼을 구현하는 방법을 살펴보았습니다. zod를 사용하면 타입 안전성을 유지하면서도 직관적으로 유효성 검사를 적용할 수 있습니다.

✨ Zod를 활용하면 좋은 이유

  • 복잡한 유효성 검사 로직을 간결하게 표현할 수 있음
  • react-hook-form과 쉽게 통합 가능
  • TypeScript와 완벽한 호환
profile
뭔 생각을 해. 그냥 하는 거지 뭐

0개의 댓글