[TIL] Zod의 refine과 superRefine

Zod의 refine과 superRefine

핵심 개념

두 메서드 모두 커스텀 유효성 검증을 추가하는 기능이지만, 복잡도와 제어 수준이 다릅니다.

refine - 간단한 커스텀 검증

언제 사용하나요?

  • 단순한 조건 검증이 필요할 때
  • 하나의 검증 규칙만 추가하면 될 때

특징

  • 검증 함수가 boolean을 반환
  • true면 통과, false면 에러
  • 에러 메시지와 경로를 간단하게 지정 가능

예시 코드

const passwordSchema = z.string().refine((val) => val.length >= 8, {
  message: '비밀번호는 8자 이상이어야 합니다',
})

const userSchema = z
  .object({
    password: z.string(),
    confirmPassword: z.string(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: '비밀번호가 일치하지 않습니다',
    path: ['confirmPassword'], // 에러가 표시될 필드
  })

superRefine - 복잡한 커스텀 검증

언제 사용하나요?

  • 여러 개의 에러를 동시에 발생시켜야 할 때
  • 조건부로 다른 필드를 검증해야 할 때
  • 더 세밀한 제어가 필요할 때

특징

  • ctx (context) 객체를 통해 에러를 직접 추가
  • ctx.addIssue()로 여러 에러를 한 번에 발생 가능
  • 더 복잡한 검증 로직 구현 가능

예시 코드

const formSchema = z
  .object({
    email: z.string(),
    password: z.string(),
    confirmPassword: z.string(),
  })
  .superRefine((data, ctx) => {
    // 여러 검증을 동시에 수행
    if (data.password !== data.confirmPassword) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: '비밀번호가 일치하지 않습니다',
        path: ['confirmPassword'],
      })
    }

    if (data.password.length < 8) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: '비밀번호는 8자 이상이어야 합니다',
        path: ['password'],
      })
    }

    // 조건부 검증
    if (data.email.includes('admin') && data.password.length < 12) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: '관리자 계정은 12자 이상의 비밀번호가 필요합니다',
        path: ['password'],
      })
    }
  })

선택 기준

상황 추천 메서드
단순한 true/false 검증 refine
하나의 에러만 발생 refine
여러 필드에 동시 에러 발생 superRefine
복잡한 조건부 검증 superRefine
에러 코드 커스터마이징 superRefine

실전 팁

  1. 체이닝 가능: 두 메서드 모두 여러 번 체이닝 가능
  2. 성능: 간단한 경우 refine이 더 가독성 좋음
  3. 에러 메시지: 사용자에게 명확한 피드백을 줄 수 있도록 구체적으로 작성
  4. path 지정: React Hook Form 등과 함께 사용할 때 정확한 필드에 에러 표시 가능

마치며

간단한 검증은 refine으로, 복잡한 검증은 superRefine으로 처리하면서 상황에 맞게 선택하여 사용하세요!