[TIL] React에서 react-hook-form과 zod를 사용하는 이유

승민·2025년 3월 8일
0

TIL

목록 보기
2/20

기존 방식의 문제점 (useState를 활용한 폼 관리)

React에서 폼을 관리하는 가장 기본적인 방법은 useState를 활용하는 것입니다. 하지만 이 방식은 다음과 같은 단점이 존재합니다.

  1. 렌더링 과부하: 입력값이 변경될 때마다 useState가 갱신되면서 불필요한 리렌더링이 발생한다.
  2. 유효성 검사 코드의 복잡성: useState로 개별 상태를 관리하면 각 필드의 유효성을 검사하는 코드가 길어지고, 가독성이 떨어진다.
  3. 확장성이 부족함: 대규모 폼을 만들 경우, 개별 상태를 추적하는 것이 복잡해진다.
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errors, setErrors] = useState({});

const handleSubmit = (e) => {
  e.preventDefault();
  let newErrors = {};
  if (!email.includes("@")) newErrors.email = "유효한 이메일을 입력하세요";
  if (password.length < 6) newErrors.password = "비밀번호는 6자 이상이어야 합니다";
  setErrors(newErrors);
};

이러한 문제를 해결하기 위해 react-hook-form을 사용합니다.

react-hook-form (비제어 컴포넌트를 활용한 렌더링 최적화)

react-hook-form비제어 컴포넌트(Uncontrolled Components) 기반으로 동작하는 라이브러리로, useState 없이 ref를 활용해 폼 상태를 관리합니다.

비제어 컴포넌트 기반이라 성능이 좋다.

  • useState를 사용하지 않고, ref를 통해 직접 DOM 값을 참조한다.
  • 입력값 변경 시 전체 컴포넌트가 리렌더링되지 않아 성능이 개선된다.
  • useForm을 사용하면 각 필드가 독립적으로 상태를 관리하여 최적화된다.

react-hook-form 기본 사용법

import { useForm } from "react-hook-form";

const MyForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm();
  
  const onSubmit = (data) => {
    console.log(data);
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email", { required: "이메일을 입력하세요" })} />
      {errors.email && <p>{errors.email.message}</p>}
      <button type="submit">제출</button>
    </form>
  );
};

하지만, react-hook-form에도 한계가 있습니다.

  1. 유효성 검사 코드가 길어진다: register에 개별적으로 검증 로직을 작성해야 한다.
  2. 복잡한 검증이 어렵다: 여러 필드 간 관계를 고려한 검증을 하려면 추가적인 로직이 필요하다.

이를 해결하기 위해 zod를 도입합니다.

zod (스키마 기반 검증으로 코드 간소화)

zod는 TypeScript와 궁합이 좋은 스키마 기반 유효성 검사 라이브러리로, react-hook-form과 결합하여 유효성 검사 로직을 깔끔하게 정리할 수 있습니다.

zod를 사용한 폼 유효성 검사

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

// Zod 스키마 정의
const schema = z.object({
  email: z.string().email("유효한 이메일을 입력하세요"),
  password: z.string().min(6, "비밀번호는 6자 이상이어야 합니다"),
});

const MyForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema), // Zod와 연결
  });
  
  const onSubmit = (data) => {
    console.log(data);
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email")} />
      {errors.email && <p>{errors.email.message}</p>}
      
      <input {...register("password")} type="password" />
      {errors.password && <p>{errors.password.message}</p>}
      
      <button type="submit">제출</button>
    </form>
  );
};

왜 zod를 사용할까?

  1. 코드 가독성 향상: 유효성 검사 로직을 스키마로 따로 관리하여 코드가 깔끔해진다.
  2. 중복 코드 제거: 필드별 검증을 개별적으로 처리할 필요 없이, 한 번에 해결 가능.
  3. 확장성 증가: 복잡한 데이터 구조 및 관계형 검증을 쉽게 수행할 수 있다.

결론

  1. 기존 useState를 이용한 폼 관리는 렌더링 성능 저하유효성 검사 코드 증가 문제를 초래한다.
  2. react-hook-form을 사용하면 비제어 컴포넌트 기반으로 렌더링 성능을 최적화할 수 있다.
  3. 하지만 react-hook-form만 사용하면 유효성 검사 코드가 길어지는 단점이 존재한다.
  4. 이를 해결하기 위해 zod를 도입하면 스키마 기반 검증으로 코드를 간결하게 유지할 수 있다.

결과적으로 react-hook-form + zod 조합을 사용하면 렌더링 성능을 유지하면서도 유효성 검증을 효율적으로 수행할 수 있다.

참고 자료

[React] 제어 컴포넌트(Controlled Component)와 비제어 컴포넌트(Uncontrolled Component)
React-hook-form 사용 이유와 정리

0개의 댓글