[Next.js] Zod를 이용한 React-hook-form 유효성 검증하기 +) 중복되는 zod 스키마 리팩토링!

youznn·2024년 8월 8일
0

Next JS

목록 보기
2/4

zod란 무엇일까?

Zod is a TypeScript-first schema declaration and validation library.

공식문서에 나와있는 그대로 해석하자면, 타입스크립트 친화적인 zod를 이용해 스키마를 정의하고 검증할 수 있습니다.

저희 프로젝트의 Admin page에서는 react-hook-form과 Apollo GraphQL이 사용되고 있습니다.

보시다시피 form 제출 전 유효성 검사가 필수로 이루어져야 하며, 유효성 검사를 해야 하는 항목들이 많은 것을 확인하실 수 있습니다. 이를 위해 저희 팀은 zod 라이브러리를 도입하여 런타임 환경에서도 type error를 감지하며 유효성 검사를 진행하고 있습니다.


리팩토링!

이전 포스팅에서 react-hook-form의 useForm을 리팩토링한 과정을 적었습니다! 리팩토링 과정에서 zod schema 또한 create, edit에서 사용되고 있었습니다.

두 페이지의 zod schema는 유사한 듯 다른 점이 있었습니다. 코드의 간결성과 유지보수성을 위해, 기존 page에 있던 zod schema를 utils.ts 파일에 따로 빼 정리했습니다.

utils.ts

import { levels, languages } from '@/lib/constants'
import { z } from 'zod'

const commonSchema = z.object({
  title: z.string().min(1, { message: 'required' }).max(200),
  isVisible: z.boolean(),
  difficulty: z.enum(levels),
  languages: z.array(z.enum(languages)).min(1),
  description: z
    .string()
    .min(1)
    .refine((value) => value !== '<p></p>'),
  inputDescription: z
    .string()
    .min(1)
    .refine((value) => value !== '<p></p>'),
  outputDescription: z
    .string()
    .min(1)
    .refine((value) => value !== '<p></p>'),
  testcases: z
    .array(
      z.object({
        input: z.string().min(1),
        output: z.string().min(1)
      })
    )
    .min(1),
  timeLimit: z.number().min(0),
  memoryLimit: z.number().min(0),
  hint: z.string().optional(),
  source: z.string().optional(),
  template: z
    .array(
      z
        .object({
          language: z.enum([
            'C',
            'Cpp',
            'Golang',
            'Java',
            'Python2',
            'Python3'
          ]),
          code: z.array(
            z.object({
              id: z.number(),
              text: z.string(),
              locked: z.boolean()
            })
          )
        })
        .optional()
    )
    .optional()
})

export const editSchema = commonSchema.extend({
  id: z.number(),
  tags: z
    .object({ create: z.array(z.number()), delete: z.array(z.number()) })
    .optional(),
  samples: z.object({
    create: z
      .array(z.object({ input: z.string().min(1), output: z.string().min(1) }))
      .min(1),
    delete: z.array(z.number().optional())
  })
})

export const createSchema = commonSchema.extend({
  tagIds: z.array(z.number()),
  samples: z
    .array(
      z.object({
        input: z.string().min(1),
        output: z.string().min(1)
      })
    )
    .min(1)
})

위 코드에서 commonSchema는 공통된 구조를 정의하고 있으며, editSchema와 createSchema는 commonSchema를 확장하여 각각 필요한 추가 필드를 정의하고 있습니다. 코드의 중복은 줄고, 관리는 쉬워지는 것입니다.

extend 메서드는 기존의 스키마를 기반으로 새로운 스키마를 확장할 때 사용됩니다. 예를 들어, editSchema와 createSchema는 공통된 필드를 가지면서도 서로 다른 추가 필드들을 포함하고 있습니다.

또한, optional() 메서드를 사용하여 필드가 선택적(optional)임을 지정할 수 있습니다. 이를 통해 해당 필드가 존재하지 않아도 검증을 통과할 수 있습니다.

zod 내의 message를 활용하면, 해당하는 유효성 검사를 통과하지 못했을 때의 에러 메시지를 직접 지정해 줄 수 있습니다.


react-hook-form과 사용하기

  const methods = useForm<CreateProblemInput>({
    resolver: zodResolver(createSchema),
    defaultValues: {
      difficulty: Level.Level1,
      tagIds: [],
      samples: [{ input: '', output: '' }],
      testcases: [{ input: '', output: '' }],
      hint: '',
      source: '',
      template: [],
      isVisible: true
    }
  })

위 코드처럼 react-hook-form을 이용할 때, resolverzodResolver(Schema) 형태로 구현합니다. 이때 유효성 검사를 통과하지 않으면

const { handleSubmit, setValue, getValues } = methods

다음의 submit이 동작하지 않으니, 디버깅 시 유의해야 합니다.


마치며 ☺️

Zod를 사용하여 스키마 선언과 유효성 검사를 쉽게 처리할 수 있으며, 타입 안전성을 높일 수 있습니다. 이를 통해 개발 생산성을 높이고 버그를 줄일 수 있었습니다! 아래와 같이 이전에 바닐라 자바스크립트로 하나하나 유효성 검사를 해 본 적이 있었는데요,

//reference: mdn
const emailRegExp =
  /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;

zod 라이브러를 이용해 간편하고, 가독성 좋고, 확장성까지 좋은 유효성 검사를 진행할 수 있어 럭키입니다!

profile
https://github.com/youznn

0개의 댓글