Will find a way
Zod : 타입 정의와 데이터 검증을 더 쉽게 한다? 본문
들어가기 전
TypeScript는 코드를 미리 검증해서 오류를 줄이고, 개발을 더 빠르고 안정적으로 만드는 도구다.
가장 좋은 장점 중 하나는 명확한 타입 정의로 큰 프로젝트 유지보수가 가능하다는 점이다.
타입을 정의함으로써 데이터 유효성 검증을 할 수 있게된다. 그리고 클라이언트와 서버간의 통신 사용자가 입력한 데이터
API응답 데이터를 검증하는데 이 작업은 필요하다.
TypeScript는 컴파일 시점에서 타입은 검증하지만, 런타임에서 발생하는 에러는 방지하기가 어렵다.
(런타임은 타입스크립트가 아닌 자바스크립트 실행이기 때문이다.)
Zod를 사용하면 이를 해결할 수 있다. 오늘은 Zod에 대해서 알아보겠다.
Zod 란?
Zod는 스키마 선언 및 유효성 검사 라이브러리다.
Zod는 앞서 말했듯이 타입스크립트의 런타임에서 발생하는 에러를 방지하지 못하는 한계 때문에 생겨났다.
*스키마(schema)는 데이터의 형태 및 구조
중요하다고 생각해서 한 번더 강조해본다.
런타임 단계에서는 타입스크립트가 아닌 자바스크립트이기 때문에 에러를 발견하지 못한다.
스키마(Schema) 정의
import { z } from 'zod';
const Student = z.object({
name: z.string(),
age: z.number(),
hobby: z.string(),
email: z.string().email(),
});
스키마를 객체 형태로 생성하고 객체의 값의 type을 z.string()처럼 명시한다.
스키마 작성은 직관적이라 많은 설명을 필요로하지 않을거 같다.
그리고 .email() 을 이용하면 이메일 형식까지도 확인해준다.
".email() 너무 직관적이고 편하다..."
유효성 검사
// .parse() 는 zod에서 제공하는 메서드로 유효성을 검사 실행을 한다. (실패 시 예외 throw)
Student.parse({
name: "Jaka",
age: 32,
hobby: "pingp ong",
email: "jaka@dev.com",
}); // 통과
Student.parse({
name: "Jaka",
age: 32,
hobby: "ping pong",
email: "jaka123",
}); // 에러 : 이메일 양식이 아님
직관적인 덕분에 많은 설명을 필요로 하지 않는다.
사용방법은 여기서 마치고 자주 쓰이는 zod 메서드에 대해서만 간단하게 알아보자.
자주 쓰이는 메서드
메서드 | 설명 | 예시 |
.string() | 문자열 타입 | z.string() |
.number() | 숫자 타입 | z.number() |
.boolean() | 불리언 타입 | z.boolean() |
.array() | 배열 타입 | z.array(z.string()) |
.object() | 객체 타입 | z.object({ ... }) |
.optional() | 필수 아님 (undefined 허용) | z.string().optional() |
.nullable() | null 허용 | z.string().nullable() |
.min(n) / .max(n) | 문자열 / 숫자 최소.최대 길이 | z.string().min(3).max(6) // 최소 3 ~ 6자 문자 |
.regax() | 정규식 검사 | z.string().regex(/^[0-9]{4}$/) // 0 ~ 9까지 4자리 숫자 |
.refine() | 커스텀 유효성 검사 | z.string().refine(val => val.includes('@')) |
.default() | 기본값 설정 | z.string().default('기본값') |
.parse() | 유효성 검사 실행 (실패 시 예외 throw) | schema.parse(data) |
.safeParse() | 유효성 검사 실행 (성공/실패 여부만 반환) | schema.safeParse(data) |
z.infer<typeof schema> | 타입스크립트 타입 추론 | type Form = z.infer<typeof schema> |
실제 예시
const userSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
nickname: z.string().regex(^[a-zA-Z가-힣]+$/), // 정규식 영문 한글만 가능
});
type UserForm = z.infer<typeof userSchema>;
.refine()
z.object({
password: z.string(),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
path: ['confirmPassword'], // 에러 메시지를 연결할 필드명을 명시하는 옵션
message: '비밀번호가 일치하지 않습니다.', // 에러 메시지
});
// refine은 스키마 단위의 커스텀 유효성 검사를 할 수 있다.
// 로그인/회원가입, 관계형 검증에 자주 사용한다.
사용법만 간단히 기술하고 .refine() 에 대해서 자세한건 추후에 글을 수정하거나 새로 올릴 예정이다.
.safeParse() vs .parse()
schema.parse(data); // 유효하지 않으면 예외 throw (try/catch 필요)
const result = schema.safeParse(data);
if (!result.success) {
console.log(result.error);
}
// safeParse는 실패해도 에러를 throw하지 않기 때무에 서버 코드에서 더 안전하다.
사용 흐름
스키마 정의 -> 타입 추론으로 타입 안전 확보 -> .parse() 같은 것으로 검증을 실행 -> 결과 기반 UI 처리
이 정도로만 알고 있어도 zod를 사용하는데 문제 없을 것이다.
개인적으로 느낀 점
배우는 것이 느리고 익숙한것만 쫓으려는 나의 나쁜 습성을 가져서인지 간단한 라이브러리 배우는데 오래 걸렸다. 프로젝트를 진행하면서 필요로하는 기술이다 보니 어떻게 공부를 해야하나 많이 애를 먹었다. 공부를 하고 적용하는게 맞는지 아니면 적용을 하면서 공부를 하는게 맞는지 공부의 방향을 잡지 못했다. 프로젝트에 zod를 적용을 하면서 하니 훨씬 더 도움이 된다는 것을 알게 됐고 공부하는 방법을 하나 터득하게 된 계기가 됐다.
그리고 Zod와 React-hook-form과 같이 사용해보니 기존의 내가 프로젝트에 작성하던 코드가 상당히 줄어들어 효율적으로 코드를 깔끔하게 짤 수 있었고, 직관적인 코드 덕분에 보기에 편했다. 이런 것들을 경험하니 "왜 이것을 써야하는지? 쓰면 어떤게 좋은지?" 조금이라도 능동적으로 생각할 수 있는 과정이였다.
그리고 다음에 쓸 글은 이런 zod와 잘 어울리는 react hook 라이브러리인 react-hook-form에 대해서 알아보고 zod 와 어떻게 쓰일지도 포스팅하겠다. 프로젝트에는 이미 적용했지만 시간이 부족해서 블로그에는 같이 포스팅을 못했다.
'Language > Typescript' 카테고리의 다른 글
AxiosResponse<T> (Promise랑 뭐가 다른데?!) (0) | 2025.05.08 |
---|---|
[Record] 타입 정의 (React 프로젝트를 하면서) (0) | 2025.05.05 |
[TS] 전략패턴 (0) | 2024.05.02 |
[TS] 타입 어서션 / 타입 앨리어스 / 클래스 수정자 / 제네릭 (0) | 2024.04.29 |
[TS] interface & class (0) | 2024.04.24 |