본문 바로가기
React

[React] react-hook-form과 zod로 validation 유효성 검사

by jyee 2024. 11. 24.
728x90
반응형

 

회사에서 react-hook-form을 쓰는데 zod로 유효성검사를 하는데 할 때마다 헷갈렸던 부분 한번 정리해보려고 한다.

 

 

React hook form

React Hook Form은 React에서 폼을 관리하기 위한 경량 폼 라이브러리이다. 최소한의 리렌더링과 간결한 API를 통해 폼 상태를 효율적으로 관리할 수 있다.

 

주요 장점:

  1. 간단한 API: useForm 훅을 사용하여 폼 상태와 검증 로직을 선언적으로 관리할 수 있음.
  2. 퍼포먼스 최적화: 입력 필드별로 리렌더링을 최소화하여 성능을 높임.
  3. 유연한 검증: 사용자 정의 검증 로직 및 다양한 검증 라이브러리와 통합 가능.

 

 

React Hook Form(RHF)의 다양한 기능들

1. resolver

역할: 외부 검증 라이브러리(Zod, Yup 등)를 React Hook Form과 통합하는 기능.

쉽게 설명하면 검증로직을 외부에서 가져와서 자동으로 처리해주는 것

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

const schema = z.object({
  email: z.string().email('유효한 이메일을 입력하세요.'),
});

const { register, handleSubmit, formState } = useForm({
  resolver: zodResolver(schema), // 외부 라이브러리로 검증
});

 

 

2. register

역할: 폼 필드(input, select 등)를 React Hook Form에 연결.
HTML 요소에 register를 사용하면 해당 필드의 상태를 자동으로 관리

<input {...register('email')} />

3. watch

역할: 특정 입력 필드나 전체 폼 상태의 변경을 실시간으로 추적.
값이 변경될 때마다 최신 값을 반환함

const emailValue = watch('email'); // 특정 필드 추적
const allValues = watch(); // 전체 필드 추적

4. mode 관련 

역할: mode 옵션으로 검증(validation)이 언제 실행될지를 설정

  • onSubmit (기본값): 폼 제출 시 검증.
  • onChange: 입력 값이 변경될 때마다 검증.
  • onBlur: 입력 필드에서 포커스가 벗어날 때 검증.
  • all: onChange와 onBlur 모두 실행.
const { register, handleSubmit, formState } = useForm({
  mode: 'onChange', // 입력할 때마다 검증
});

 

개인적으로 onChange랑 onBlur의 차이가 뭔지 헷갈렸다. 둘 다 입력값이 변경될때 실시간으로 검증되는 것 같았기 때문이다.

그래서 찾아보니, 

 

onBlur포커스가 빠져나갈 때 실행된다. 

입력필드에서 사용자가 input필드가 아닌 다른 곳을 클릭하거나 탭을 이동할 때 즉, 포커스가 벗어난 경우 실행된다.

사용자가 입력을 끝낸 후 검증, 입력 도중 에러 메시지를 보여주지 않고 최소한의 인터렉션만 제공하고 싶을 때 사용 된다. 

적은 검증 빈도라 성능에 유리하다 

ex.이메일 형식 확인, 필수 입력값 체크 

 

onChange값이 변경될 때 실행된다. 

입력필드의 값을 실시간으로 확인하거나 검증할 때, 자동완성, 검색 기능 등 입력값에 반응하는 로직이 필요할때 사용된다

값이 바뀔 때마다 실행되어 성능 부담이 될 수도 있다.  

ex.검색 추천어, 실시간 비밀번호 검증

 

5. handleSubmit

역할: 폼 데이터를 제출하는 함수

제출할때 검증하고 데이터를 넘겨줌!
유효성 검사를 통과한 경우에만 onSubmit 콜백을 호출함

예시:

 
const onSubmit = (data) => console.log(data);
<form onSubmit={handleSubmit(onSubmit)}>
  <button type="submit">제출</button>
</form>

6. formState

역할: 폼의 상태를 담고 있는 객체로, 검증 상태와 입력값 변경 여부 등을 확인.

주요 속성:

  • errors: 각 필드의 검증 에러 정보.
  • isDirty: 폼이 수정되었는지 여부.
  • isValid: 폼이 유효한지 여부.

7. reset

역할: 폼 상태를 초기화.
기본값으로 돌아가거나 특정 값으로 폼을 리셋

 
const { reset } = useForm();

<button onClick={() => reset()}>초기화</button>

8. setValue

역할: 특정 필드의 값을 프로그래밍 방식으로 설정.
유저가 직접 입력하지 않아도 내가 원하는 값을 강제로 설정할때 사용

setValue('email', 'test@example.com'); // email 필드에 값 설정

 

9. getValues

역할: 현재 입력된 값을 가져옵니다.
유효성 검증과 상관없이 현재 값을 즉시 확인할 때 

const values = getValues();
console.log(values);

 

10. defaultValues

역할: 폼 필드의 초기 값을 설정.

//회원가입 폼 상태 관리
  const form = useForm({
    defaultValues: {
      name: '',
      email: '',
      password: '',
      confirmPassword: '',
      phone: '',
    },
    mode: 'onChange',
  });

Zod란?


Zod는 TypeScript를 우선으로 설계된 스키마 선언 및 데이터 검증 라이브러리.

 Zod를 사용하면 데이터 검증과 타입 선언을 한 번에 처리할 수 있어, 중복된 타입 선언을 제거하고 효율적인 코드를 작성할 수 있다. 한 번 선언된 스키마는 자동으로 정적 TypeScript 타입을 추론하여, 타입 안전성을 보장한다.

 

 

Zod의 주요 장점:

  1. 종속성 없음
    Zod는 외부 라이브러리에 의존하지 않아 가볍고 독립적으로 사용할 수 있다.
  2. 다양한 환경에서 작동
    Node.js와 최신 브라우저를 포함한 대부분의 JavaScript 환경에서 사용할 수 있다.
  3. 작은 용량
    라이브러리 자체가 경량으로 설계 되었다.
  4. 불변성(Immutability)
    모든 Zod 객체는 불변성을 가지며, 원래 객체를 수정하지 않고 새로운 객체를 반환한다.
  5. 간결하고 체인 가능한 인터페이스
    직관적이고 선언적인 API를 제공하여 가독성이 높고, 복잡한 검증 로직도 간단히 작성할 수 있다.
const PasswordSchema = z.string().min(8).max(20).regex(/[A-Z]/, '대문자 필요');

 

 


 

Zod의 주요 메서드와 사용법

1) 기본 스키마 생성 

다양한 기본 데이터 타입을 선언한다.

const StringSchema = z.string(); 
const NumberSchema = z.number();
const BooleanSchema = z.boolean();

 

2) 객체 스키마 

객체의 키와 값을 정의 

const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().optional(),
});

 

3) 조건 추가

const AgeSchema = z.number().min(18, '최소 나이는 18세입니다').max(100);

 

4) 선택적 

필드가 선택사항일 경우에는 optional()을 쓰면 된다.

const OptionalField = z.string().optional();

 

5) 커스텀 검증 

const CustomSchema = z.string().refine((val) => val.startsWith('z'), {
  message: 'z로 시작해야 합니다.',
});

 

 


뭐가 너무 많아서 헷갈렸던 거....

refine, superRefine, transform 언제 쓰이는건가? 

refine

  • 추가 조건을 검증할 때 사용.
  • 조건이 통과되지 않으면 에러를 반환.
const schema = z.string().refine((val) => val.length >= 5, {
  message: "최소 5자 이상이어야 합니다.",
});

schema.parse("hello"); // ✅ 성공
schema.parse("hi"); // ❌ 에러

 

superRefine

refine보다 강력한 기능으로 객체 전체를 기반으로 복잡한 조건을 추가할 때 사용

여러 에러메시지를 반환하거나, 특정 필드에 에러를 연결 할 수도 있다

const schema = z.object({
  password: z.string(),
  confirmPassword: z.string(),
}).superRefine((data, ctx) => {
  if (data.password !== data.confirmPassword) {
    ctx.addIssue({
      path: ['confirmPassword'], // 에러를 특정 필드에 연결
      message: '비밀번호가 일치하지 않습니다.',
    });
  }
});

schema.parse({ password: '1234', confirmPassword: '1234' }); // ✅ 성공
schema.parse({ password: '1234', confirmPassword: 'abcd' }); 
// ❌ 에러: confirmPassword - 비밀번호가 일치하지 않습니다.

 

transform

  • 검증 후 데이터를 변환.
const schema = z.string().transform((val) => val.toUpperCase());

schema.parse("hello"); // ✅ "HELLO" 반환

 

 

 

 

1. Zod 설치

npm install react-hook-form zod @hookform/resolvers    	// npm
yarn add react-hook-form zod @hookform/resolvers 		// yarn

 

2.  사용 

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

// Zod 스키마 정의
const schema = z.object({
  username: z.string().min(1, '사용자 이름은 필수입니다.'),
  email: z.string().email('유효한 이메일 주소를 입력하세요.'),
  password: z.string().min(6, '비밀번호는 최소 6자 이상이어야 합니다.'),
});

type FormData = z.infer<typeof schema>;

const App: React.FC = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data: FormData) => {
    console.log('폼 데이터:', data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>사용자 이름</label>
        <input {...register('username')} />
        {errors.username && <p>{errors.username.message}</p>}
      </div>

      <div>
        <label>이메일</label>
        <input {...register('email')} />
        {errors.email && <p>{errors.email.message}</p>}
      </div>

      <div>
        <label>비밀번호</label>
        <input type="password" {...register('password')} />
        {errors.password && <p>{errors.password.message}</p>}
      </div>

      <button type="submit">제출</button>
    </form>
  );
};

export default App;

 

728x90
반응형