로그인, 회원가입, 게시글 작성 등 사용자 입력이 필요한 화면에서는 폼 유효성 검사가 필수입니다.
React 프로젝트에서 가장 많이 쓰이는 폼 라이브러리는 `React Hook Form`,
그리고 유효성 검증 라이브러리는 `Zod` 또는 `Yup`입니다.
실무에서는 이렇게 씁니다.
React Hook Form은 폼 상태 관리(에러, 입력 값 등)
Zod or Yup은 유효성 검증 스키마
1. React Hook Form 이란?
React에서 가볍고 빠른 폼 상태 관리를 할 수 있게 해주는 라이브러리
- useForm 훅으로 간편하게 폼 상태 관리
- 불필요한 리렌더링 최소화 (성능 우수)
- Yup, Zod, Joi 등과 함께 사용 가능
npm install react-hook-form
2. Zod 란?
zod는 타입스크립트를 기반으로 한 런타임 데이터 유효성 검사(validation) 라이브러리입니다.
입력 데이터가 예상한 형태인지 검사하고, 동시에 타입도 추론해주는 강력한 도구입니다.
- 스키마를 기반으로 자동 타입 추론 가능
- 런타임에서도 타입 안전성 보장
- React Hook Form과 궁합이 매우 좋음
npm install zod @hookform/resolvers
📝 기본 개념
z.object()란?
z.object()는 객체 스키마(schema)를 정의하는 함수입니다.
즉, 객체 형태의 데이터가 어떤 속성을 가져야하고, 각 속성이 어떤 타입인지 명시합니다.
※ 스키마(schema)란?
데이터의 구조와 규칙을 정의한 청사진입니다.
쉽게 말하면, "이 데이터는 어떤 모양이어야 하고, 어떤 조건을 만족해야 하는지 미리 정해놓은 설계도"
예제1 : 간단한 객체 스키마
import { z } from "zod";
const userSchema = z.object({
name: z.string(),
age: z.number(),
});
-> 다음과 같은 타입을 검증합니다.
const validUser = {
name: "Alice",
age: 25,
};
const invalidUser = {
name: "Bob",
age: "25", // ❌ 에러: age는 number여야 함
};
타입 자동 추론(z.infer)
Zod은 타입도 자동으로 추론해줍니다.
type User = z.infer<typeof userSchema>;
// 결과:
// type User = {
// name: string;
// age: number;
// }
조건 추가하기
Zod 스키마는 조건도 붙일 수 있습니다.
const loginSchema = z.object({
email: z.string().email("이메일 형식이 올바르지 않아요."),
password: z.string().min(8, "비밀번호는 최소 8자리"),
});
.refine()로 커스텀 조건 추가
const passwordSchema = z
.string()
.min(8)
.refine((val) => /[A-Z]/.test(val), {
message: "대문자를 포함해야 합니다.",
});
📌 Zod vs Yup 비교
실무에서는
- TypeScript 기반 프로젝트라면 Zod,
- JavaScript-only라면 Yop을 많이 씀.
| 항목 | Zod | Yup |
| 타입 추론 | ✅ 자동 추론 (Zod만 가능) | ❌ 타입 수동 지정 필요 |
| TS 친화도 | ✅ 매우 좋음 | 보통 |
| 체이닝 문법 | ✅ 명시적, 가독성 높음 | ✅ 자연스럽고 직관적 |
| 문법 스타일 | 함수 기반 (z.string()) | 체이닝 기반 (yup.string().required()) |
| 오류 메시지 커스터마이징 | 쉽고 명확함 | 가능하지만 비교적 복잡 |
| 유지보수 상태 | 활발 (Zod 인기 급상승) | 안정적이나 다소 정체됨 |
3. 예제 : 로그인 폼(React Hook Form + Zod)
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const loginSchema = z.object({
email: z.string().email("올바른 이메일 형식이 아닙니다."),
password: z.string().min(6, "비밀번호는 최소 6자 이상이어야 합니다."),
});
type LoginFormData = z.infer<typeof loginSchema>;
export default function LoginForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
});
const onSubmit = (data: LoginFormData) => {
console.log("Login 성공", data);
// API 호출
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<input type="text" placeholder="Email" {...register("email")} />
{errors.email && <p>{errors.email.message}</p>}
<input type="password" placeholder="Password" {...register("password")} />
{errors.password && <p>{errors.password.message}</p>}
<button type="submit">Login</button>
</form>
);
}'FE > React' 카테고리의 다른 글
| [Next.js Middleware 완전 정복] 로그인 인증, 리디렉션, 권한 제어까지 실무 예제로! (0) | 2025.05.09 |
|---|---|
| [Next.js] 서버 컴포넌트 vs 클라이언트 컴포넌트 – 개념, 차이, 예시까지! (0) | 2025.05.09 |
| shadcn/ui + lucide-react 소개 & 사용법 (0) | 2025.05.08 |
| [React Query] (0) | 2025.01.23 |
| npm(node package manager) (0) | 2023.10.04 |