티스토리 뷰
1. refine
refine은 단일 필드나 스키마의 특정 값에 대한 검증을 할 때 사용되며, 아래의 코드처럼 이용할 수 있다.
const formSchema = z
.object({
username: z
.string({
invalid_type_error: "Username must be a string!",
required_error: "No username",
})
.min(5, "Way too short")
.max(10, "That is too long")
.trim()
.toLowerCase(),
email: z.string().email().toLowerCase(),
password: z
.string()
.min(PASSWORD_MIN_LENGTH)
.regex(PASSWORD_REGEX, PASSWORD_REGEX_ERROR),
confirmPassword: z.string().min(PASSWORD_MIN_LENGTH),
})
.refine(
async ({ username }) => {
const user = await db.user.findUnique({
where: {
username,
},
// select: user에서 가져오고 싶은 것들 선택
select: {
id: true, // user에서 id만 가져옴 (email 등은 안 가져옴)
},
});
return !Boolean(user);
},
{ message: "이미 해당 username이 존재합니다.", path: ["username"] }
)
.refine(
async ({ email }) => {
const user = await db.user.findUnique({
where: {
email,
},
select: {
id: true,
},
});
return !Boolean(user);
},
{ message: "이미 해당 email이 존재합니다.", path: ["email"] }
)
.refine(({ password, confirmPassword }) => password === confirmPassword, {
message: "Two passwords should be equal",
path: ["confirmPassword"],
});
refine의 첫 번째 인자는 검증을 위한 함수를, 두 번째 인자는 검증에 실패했을 때 필요한 메시지이다. 그리고 위 코드는 3개의 refine을 통해 이미 존재하는 username, email이 있는지, 비밀번호를 제대로 입력했는지를 검증한다. 하지만 refine은 검증에 실패해도 다음 refine을 수행한다.

예를 들어, 이미 test01이라는 username과 test01@email.com 이라는 이메일을 이용하는 계정이 있고, 이 username과 email을 이용해 계정을 생성하는 경우, 3개의 refine을 모두 실행해 3개의 에러 메시지를 확인할 수 있다.


이는 별로 바람직하지 않을 수도 있다. 왜냐하면 username과 email을 검증할 때 db에서 user를 확인하는데, 이 경우에는 이미 username이 중복되는 것을 확인했음에도 email도 확인하기 때문이다. 그렇기에 지금처럼 규모가 작을 경우에는 문제가 없지만 규모가 커지면 좋지 않을 가능성이 있다.
2. superRefine
이런 경우 refine 대신 이용할 수 있는 것이 superRefine이다. 기존 refine과 다르게 superRefine은 스키마 전체 혹은 여러 필드 간의 상호 관계에 따라 검증할 때 이용할 수 있으며 addIssue를 통해 원하는 필드에 오류를 수동으로 지정할 수 있다. 그리고 검증에 실패할 경우, 다음 refine을 실행하지 않게 할 수도 있다.
const formSchema = z
.object({
username: z
.string({
invalid_type_error: "Username must be a string!",
required_error: "No username",
})
.min(5, "Way too short")
.max(10, "That is too long")
.trim()
.toLowerCase(),
email: z.string().email().toLowerCase(),
/* .refine(
checkUniqueEmail,
"There is an account already registered with that email"
),*/
password: z
.string()
.min(PASSWORD_MIN_LENGTH)
.regex(PASSWORD_REGEX, PASSWORD_REGEX_ERROR),
confirmPassword: z.string().min(PASSWORD_MIN_LENGTH),
})
.superRefine(async ({ username }, ctx) => {
const user = await db.user.findUnique({
where: {
username,
},
select: {
id: true,
},
});
if (user) {
ctx.addIssue({
code: "custom",
message: "This username is already taken",
path: ["username"], // path가 없으면 이 에러 메시지는 formErrors로 들어감
fatal: true,
});
return z.NEVER; // z.NEVER를 return하는데 fatal이 true라면 이후의 refine을 실행하지 않고 끝냄
}
})
.superRefine(async ({ email }, context) => {
const user = await db.user.findUnique({
where: { email },
select: { id: true },
});
if (user) {
context.addIssue({
code: "custom",
message: "This email is already taken",
path: ["email"],
fatal: true,
});
return z.NEVER;
}
})
.superRefine(({ password, confirmPassword }, ctx) => {
if (password !== confirmPassword) {
// ctx로 특정 필드에 에러 메시지 추가
ctx.addIssue({
code: "custom",
message: "Two passwords should be equal",
path: ["confirmPassword"],
});
}
});
superRefine의 첫 번째 인자는 z.object로 설정한 객체, 두 번째 인자는 ctx(컨텍스트 객체)이다. 그리고 ctx.addIssue를 통해 특정 필드에 오류를 지정할 수 있다. 그리고 (위 코드처럼) 조건에 따라 fatal: true일 때 z.NEVER를 return할 경우, 다음 refine은 실행하지 않는다.


첫 번째 경우랑 입력한 값은 같지만, username의 유효성 검증이 실패했기 때문에 email 검증은 하지 않은 것을 확인할 수 있다.
3. 정리
- refine과 달리 superRefine은 여러 필드 혹은 스키마 전체에 대한 종합적인 검증을 할 수 있음
- 복잡한 논리 추가 혹은 수동으로 오류 제어할 때 유용
- 유효성 검증 실패 시 다음 refine을 실행하지 않게 설정할 수 있음
'기록' 카테고리의 다른 글
Next.js의 params, loading (1) | 2024.10.20 |
---|---|
Prisma에서 발생하는 에러 중 하나인 PrismaClientKnownRequestError 처리하기 (0) | 2024.10.19 |
Next.js에서의 useFormStatus, useFormState (0) | 2024.09.29 |
Suspense 간단히 이용해보기 (0) | 2024.09.07 |
로딩 컴포넌트 (0) | 2024.09.07 |
- Total
- Today
- Yesterday
- 리액트
- 알고리즘
- C++
- 코드스테이츠
- 동적계획법
- typescript
- BFS
- CSS
- 스택
- 비트마스킹
- themoviedb
- Next.js
- SQL
- 프로그래머스
- 구현
- async
- 백준
- aws
- 햄버거버튼
- Redux
- 순열
- 브루트포스
- 카카오맵
- 다이나믹프로그래밍
- 자바스크립트
- 넥스트js
- NextJS
- 타입스크립트
- 완전탐색
- react
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |