Zod 완벽 마스터

Zod의 핵심 개념과 실전 활용법

TypeScript중급
8시간
4개 항목
학습 진행률0 / 4 (0%)

학습 항목

1. React
Shadcn|Form|완벽|가이드
퀴즈튜토리얼
2. TypeScript
Zod|런타임|타입|검증|완벽|가이드
퀴즈튜토리얼
3. TypeScript
초급
Zod|성능|최적화|완벽|가이드
퀴즈튜토리얼
4. TypeScript
고급
tRPC|타입_세이프|API|설계|패턴
퀴즈튜토리얼
1 / 4

이미지 로딩 중...

Shadcn Form 완벽 가이드 - 슬라이드 1/13

Shadcn Form 완벽 가이드

Shadcn UI의 Form 컴포넌트와 React Hook Form, Zod를 활용한 고급 폼 구현 방법을 다룹니다. 타입 안전성과 유효성 검증, 에러 핸들링까지 실전에서 바로 사용할 수 있는 패턴을 제공합니다.


카테고리:React
언어:TypeScript
메인 태그:#React
서브 태그:
#Shadcn#ReactHookForm#Zod#FormValidation

들어가며

이 글에서는 Shadcn Form 완벽 가이드에 대해 상세히 알아보겠습니다. 총 12가지 주요 개념을 다루며, 각각의 개념에 대한 설명과 실제 코드 예제를 함께 제공합니다.

목차

  1. 기본_폼_설정
  2. Form_컴포넌트_구조
  3. 에러_메시지_표시
  4. 복잡한_스키마_검증
  5. 동적_폼_배열
  6. 비동기_검증
  7. FormDescription_활용
  8. 조건부_필드_렌더링
  9. 제출_상태_관리
  10. Select_컴포넌트_통합
  11. 파일_업로드_처리
  12. 전역_에러_처리

1. 기본_폼_설정

개요

Shadcn Form은 React Hook Form과 Zod를 기반으로 타입 안전한 폼을 구축합니다. useForm 훅에 zodResolver를 연결하여 스키마 기반 검증을 수행합니다.

코드 예제

```tsx
const formSchema = z.object({
  email: z.string().email("유효한 이메일을 입력하세요"),
  password: z.string().min(8, "8자 이상 입력하세요"),
})

const form = useForm<z.infer<typeof formSchema>>({
  resolver: zodResolver(formSchema),
  defaultValues: { email: "", password: "" },
})

### 설명

zodResolver가 Zod 스키마를 React Hook Form과 연결하고, z.infer로 타입을 자동 추론하여 타입 안전성을 보장합니다.

---

## 2. Form_컴포넌트_구조

### 개요

Form, FormField, FormItem, FormControl 등 계층적 구조로 폼 UI를 구성합니다. FormField는 Controller를 래핑하여 상태 관리를 단순화합니다.

### 코드 예제

```typescript
```tsx
<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)}>
    <FormField
      control={form.control}
      name="email"
      render={({ field }) => (
        <FormItem>
          <FormLabel>이메일</FormLabel>
          <FormControl>
            <Input {...field} />
          </FormControl>
        </FormItem>
      )}
    />
  </form>
</Form>

### 설명

FormField의 render prop이 field 객체를 제공하며, 이를 Input에 spread하여 value, onChange 등을 자동 연결합니다.

---

## 3. 에러_메시지_표시

### 개요

FormMessage 컴포넌트가 필드별 에러를 자동으로 표시합니다. Zod 스키마의 에러 메시지가 검증 실패 시 렌더링됩니다.

### 코드 예제

```typescript
```tsx
<FormField
  control={form.control}
  name="username"
  render={({ field }) => (
    <FormItem>
      <FormLabel>사용자명</FormLabel>
      <FormControl>
        <Input {...field} />
      </FormControl>
      <FormMessage />
    </FormItem>
  )}
/>

### 설명

FormMessage는 해당 필드의 에러 상태를 자동 감지하여 에러 메시지를 렌더링하며, 별도의 조건부 로직이 필요 없습니다.

---

## 4. 복잡한_스키마_검증

### 개요

Zod의 refine과 superRefine으로 커스텀 검증 로직을 추가할 수 있습니다. 여러 필드 간 관계를 검증할 때 유용합니다.

### 코드 예제

```typescript
```tsx
const schema = z.object({
  password: z.string().min(8),
  confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
  message: "비밀번호가 일치하지 않습니다",
  path: ["confirmPassword"],
})

### 설명

refine 메서드로 두 필드를 비교하고, path 옵션으로 에러를 특정 필드에 연결하여 정확한 위치에 메시지를 표시합니다.

---

## 5. 동적_폼_배열

### 개요

useFieldArray로 동적으로 추가/제거 가능한 폼 필드 배열을 관리합니다. append, remove 메서드로 간편하게 조작할 수 있습니다.

### 코드 예제

```typescript
```tsx
const { fields, append, remove } = useFieldArray({
  control: form.control,
  name: "items",
})

{fields.map((field, index) => (
  <FormField key={field.id} name={`items.${index}.name`}
    render={({ field }) => <Input {...field} />} />
))}
<Button onClick={() => append({ name: "" })}>추가</Button>

### 설명

useFieldArray가 배열 상태를 관리하고, 각 필드는 고유 id로 추적되어 React의 key 최적화를 활용합니다.

---

## 6. 비동기_검증

### 개요

Zod의 .refine 또는 superRefine에서 Promise를 반환하여 API 호출 등 비동기 검증을 수행할 수 있습니다.

### 코드 예제

```typescript
```tsx
const schema = z.object({
  username: z.string().refine(
    async (val) => {
      const res = await fetch(`/api/check?name=${val}`)
      return res.ok
    },
    { message: "이미 사용중인 사용자명입니다" }
  ),
})

### 설명

refine 콜백에서 async 함수를 사용하면 폼 제출 전 비동기 검증이 실행되며, 결과에 따라 에러를 표시합니다.

---

## 7. FormDescription_활용

### 개요

FormDescription으로 필드 설명을 추가하여 사용자 가이드를 제공합니다. 접근성과 UX를 동시에 개선할 수 있습니다.

### 코드 예제

```typescript
```tsx
<FormField
  control={form.control}
  name="bio"
  render={({ field }) => (
    <FormItem>
      <FormLabel>자기소개</FormLabel>
      <FormControl>
        <Textarea {...field} />
      </FormControl>
      <FormDescription>
        200자 이내로 작성해주세요.
      </FormDescription>
    </FormItem>
  )}
/>

### 설명

FormDescription은 FormMessage와 별개로 항상 표시되는 도움말 텍스트로, aria-describedby로 자동 연결됩니다.

---

## 8. 조건부_필드_렌더링

### 개요

watch를 사용하여 특정 필드 값에 따라 다른 필드를 동적으로 표시/숨김 처리할 수 있습니다.

### 코드 예제

```typescript
```tsx
const accountType = form.watch("accountType")

{accountType === "business" && (
  <FormField
    control={form.control}
    name="companyName"
    render={({ field }) => (
      <FormItem>
        <FormLabel>회사명</FormLabel>
        <FormControl><Input {...field} /></FormControl>
      </FormItem>
    )}
  />
)}

### 설명

watch가 필드 값 변경을 구독하여 리렌더링을 트리거하며, 조건부 렌더링된 필드도 자동으로 폼 상태에 통합됩니다.

---

## 9. 제출_상태_관리

### 개요

formState를 통해 제출 중 상태, 에러, 터치 여부 등을 추적하고 UI에 반영할 수 있습니다.

### 코드 예제

```typescript
```tsx
const { isSubmitting, isValid } = form.formState

const onSubmit = async (data: z.infer<typeof schema>) => {
  await fetch("/api/submit", { method: "POST", body: JSON.stringify(data) })
}

<Button type="submit" disabled={isSubmitting || !isValid}>
  {isSubmitting ? "처리중..." : "제출"}
</Button>

### 설명

isSubmitting은 비동기 onSubmit 실행 중 자동으로 true가 되며, isValid는 모든 검증 통과 여부를 실시간 반영합니다.

---

## 10. Select_컴포넌트_통합

### 개요

Shadcn Select를 FormField와 통합할 때 onValueChange를 field.onChange에 연결하여 상태를 동기화합니다.

### 코드 예제

```typescript
```tsx
<FormField
  control={form.control}
  name="country"
  render={({ field }) => (
    <FormItem>
      <FormControl>
        <Select onValueChange={field.onChange} defaultValue={field.value}>
          <SelectTrigger><SelectValue /></SelectTrigger>
          <SelectContent>
            <SelectItem value="kr">한국</SelectItem>
          </SelectContent>
        </Select>
      </FormControl>
    </FormItem>
  )}
/>

### 설명

Select의 onValueChange가 field.onChange와 연결되어 값 변경이 폼 상태에 자동 반영되며, defaultValue로 초기값을 설정합니다.

---

## 11. 파일_업로드_처리

### 개요

Input type="file"을 사용할 때 FileList 타입을 Zod로 검증하고, onChange를 커스터마이징하여 파일 상태를 관리합니다.

### 코드 예제

```typescript
```tsx
const schema = z.object({
  file: z.instanceof(FileList).refine(
    (files) => files.length > 0, "파일을 선택하세요"
  ),
})

<FormControl>
  <Input type="file" {...field} value={field.value?.filename}
    onChange={(e) => field.onChange(e.target.files)} />
</FormControl>

### 설명

FileList 인스턴스를 Zod로 검증하고, onChange에서 e.target.files를 직접 전달하여 파일 객체를 폼 상태에 저장합니다.

---

## 12. 전역_에러_처리

### 개요

setError로 서버 응답 에러를 특정 필드나 root에 수동으로 설정할 수 있습니다. API 에러를 폼 UI에 통합할 때 유용합니다.

### 코드 예제

```typescript
```tsx
const onSubmit = async (data: FormData) => {
  try {
    await api.submit(data)
  } catch (error) {
    form.setError("root", {
      message: "서버 오류가 발생했습니다",
    })
  }
}

{form.formState.errors.root && <p>{form.formState.errors.root.message}</p>}

### 설명

setError의 root 키는 특정 필드가 아닌 폼 전체 에러를 나타내며, formState.errors.root로 접근하여 표시합니다.

---

## 마치며

이번 글에서는 Shadcn Form 완벽 가이드에 대해 알아보았습니다.
총 12가지 개념을 다루었으며, 각각의 사용법과 예제를 살펴보았습니다.

### 관련 태그

#React #Shadcn #ReactHookForm #Zod #FormValidation
#React#Shadcn#ReactHookForm#Zod#FormValidation