
zod ๋ ์คํค๋ง ์ ์ธ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
zod ์ ๋น์ทํ ์ญํ ์ ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก๋ joi , yup , typia ๋ฑ์ด ์๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก zod ๋ TypeScript ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ง๋ง , TypeScript์ JavaScript ๊ฐ๋ฐ ํ๊ฒฝ์์ ๋ชจ๋ ์ฌ์ฉ ํ ์ ์๋ค.
๊ณต์๋ฌธ์์์ ์ฃผ์ฅํ๋ ์ด์ ์ผ๋ก๋ ๋ค์๊ณผ ๊ฐ๋ค.
- Zero dependencies => ์์กด์ฑ์ด ์์
- Works in Node.js and all modern browsers => Node.js ๋ฐ ๋ชจ๋ ์ต์ ๋ธ๋ผ์ฐ์ ์์ ์๋
- Tiny: 8kb minified + zipped => ๋ฐ์ดํฐ 8kb ์ถ์ + ์์ถ๋จ
- Immutable: methods (e.g. .optional()) return a new instance => ๋ฉ์๋๋ค ์๋ก์ด ์ธ์คํด์ค ๋ฐํ
- Concise, chainable interface. => ๊ฐ๊ฒฐํ๊ณ ์ฐ๊ฒฐ๊ฐ๋ฅํ ์ธํฐํ์ด์ค ์ ๊ณต
- Functional approach: parse, don't validate
- Works with plain JavaScript too! You don't need to use TypeScript. => ์๋ฐ์คํฌ๋ฆฝํธ ๋ ํ์ ์คํฌ๋ฆฝํธ ๋๋ค ์ฌ์ฉ๊ฐ๋ฅ
- zod ์ค์น
npm install zod # npm yarn add zod # yarn bun add zod # bun pnpm add zod # pnpm
- ๋ฆฌ์กํธํ ํผ & resolvers ์ค์น
npm i react-hook-form npm i @hookform/resolvers
import { z } from "zod";
(์์ ๊ฐ์ด z ๋ฅผ ํตํด์ Zod์ ๊ธฐ๋ฅ์ ํ์ฉํ ์ ์๋ค.)
๊ทธ๋ผ ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก zod๋ฅผ ์ฌ์ฉํด์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํด๋ณด์!
๋ง์ ๋ฒจ๋ก๊ทธ์ ๋ค ํ์
์คํฌ๋ฆฝํธ ๋ฅผ ํ์ฉํ์ฌ zod๋ฅผ ์ฌ์ฉํ์
จ๊ธธ๋ ๋๋ ๊ฟ๊ฟํ๊ฒ ์๋ฐ์คํฌ๋ฆฝํธ ๋ก ์ฌ์ฉํด๋ณด๊ฒ ๋ค.
์ฐ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ๋ ค๋ฉด ์
๋ ฅ ํผ์ ๋ํ ์คํค๋ง๋ฅผ ์์ฑํด์ค์ผํ๋ค.
(yup ์ฌ์ฉ์์๋ ํ์ํ๋ค.)
์ฌ์ฌ์ฉ์ฑ ๋ฐ ์ฝ๋ ์์ ํธ์๋ฅผ ์ํด Schema ์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ๋นผ์ ์๋์ ๊ฐ์ด ์์ฑํ๋ค.
import { z } from "zod";
export const Schema = z.object({
email: z
.string()
.email({ message: "์ด๋ฉ์ผ ์์์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค" })
.min(1, { message: "ID๋ฅผ ์
๋ ฅํด์ฃผ์ธ์" })
.optional(),
password: z
.string()
//regex: ๋ฌธ์์ด์ด ์ฃผ์ด์ง ์ ๊ท์ ํจํด๊ณผ ์ผ์นํ๋์ง ๊ฒ์ฆ
.regex(/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).*$/, {
message: "๋์๋ฌธ์, ์ซ์, ํน์๋ฌธ์๋ฅผ ํ๋ ์ด์ ํฌํจํด ์ฃผ์ธ์",
})
.min(8, { message: "8๊ธ์ ์ด์ ์
๋ ฅํด์ฃผ์ธ์" })
.optional(),
});
์๋ง Yup ์ ์ฌ์ฉํด๋ณด์๋๋ผ๋ฉด ๋งค์ฐ ๋น์ทํ๋ค๋๊ฒ์ ์ ์ ์์๊ฒ์ด๋ค.
์ฝ๊ฐ์ ๋ฉ์๋๋ง ๋ค๋ฅด๋ค.
Object() ๋ก ๊ฐ์ฒด ํ์์ ์คํค๋ง๋ฅผ ์ ์ํ๋ค. ์ด ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ฒด์ ๊ตฌ์กฐ, ํ๋, ๊ฐ ํ๋์ ์ ํจ์ฑ๊ฒ์ฆ ์กฐ๊ฑด์ ์ ์ํ ์ ์๋ค.
๋ํ ์ฝ๋์์ ๋ณด์ด๋ optional() ๋ฉ์๋๋ ํ๋๊ฐ ํ์๊ฐ ์๋๋ผ ์ ํ์ฌํญ์ด๋ผ๊ณ ๋ช
์ํด์ฃผ๋๊ฒ์ด๋ค.
์๋ฅผ ๋ค์ด ์๋์ ๊ฐ์ ์คํค๋ง๋ฅผ ์์ฑํ๋ค๊ณ ํ๋ฉด,
z.object({ name: z.string().optional(), age: z.number().optional(), });
name๊ณผ age ํ๋๋ ๋ฌธ์์ด/์ซ์ ํ์์ด๋ฉฐ ์ ํ์ ์ด๋ค. ์ฆ ์ด ํ๋๊ฐ ์๊ฑฐ๋ ๋ฌธ์์ด/์ซ์ ์ด(๊ฐ) ์๋๋๋ผ๋ ์คํค๋ง๋ ์ ํจํจ์ ๋ช
์ํด์ฃผ๋๊ฒ์ด๋ค.
์ฆ, ์ ํ์ ํ๋๋ ํด๋น ํ๋๊ฐ ์์ด๋ ์ ์ฒด ์คํค๋ง์ ์ ํจ์ฑ์ ๋ฐฉํดํ์ง ์๋๋ค.
๋ฐ๋ผ์ ์ด ์คํค๋ง๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ ๋ ๋ค์๊ณผ ๊ฐ์ ์ํฉ๋ค์ด ๊ฐ๋ฅํ๋ค.
- {} (๋น ๊ฐ์ฒด): name๊ณผ age ํ๋๊ฐ ๋ชจ๋ ์์ผ๋ฏ๋ก ์ ํจ.
- { "name": "John" }: name์ ์ ํจํ ๋ฌธ์์ด ๊ฐ์ ๊ฐ์ง๊ณ ์๊ณ , age๋ ์์ผ๋ฏ๋ก ์ ํจ.
- { "age": 30 }: age๋ ์ ํจํ ์ซ์ ๊ฐ์ ๊ฐ์ง๊ณ ์๊ณ , name์ ์์ผ๋ฏ๋ก ์ ํจ.
- { "name": 123, "age": "thirty" }: name์ ์ ํจํ์ง ์์ ์ซ์ ๊ฐ์ด์ง๋ง ์ ํ์ ์ด๋ฏ๋ก ์ ์ฒด ์คํค๋ง๋ ์ฌ์ ํ ์ ํจ.
๋น๊ต๋ฅผ ์ํด ์๋๋ Yup ์ผ๋ก ์ด ์คํค๋ง๋ฅผ ์ฒจ๋ถํ๋ค.
import * as yup from "yup";
const schema = yup.object({
email: yup
.string()
.email("์ด๋ฉ์ผ ์์์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค")
.required("ID๋ฅผ ์
๋ ฅํด์ฃผ์ธ์"),
password: yup
.string()
.matches(
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).*$/,
"๋์๋ฌธ์, ์ซ์, ํน์๋ฌธ์๋ฅผ ํ๋ ์ด์ ํฌํจํด ์ฃผ์ธ์"
)
.min(8, "8๊ธ์ ์ด์ ์
๋ ฅํด์ฃผ์ธ์")
.required("๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์"),
});
export default schema;
์์ ๊ฐ์ด ์คํค๋ง๋ฅผ ์์ฑํ๋ค๋ฉด ์ด์ ์ง์ ์จ๋ณผ ์ฐจ๋ก์ด๋ค.
CSS ๋ฅผ ๋ค๋ก ์ฌ์ณ๋๊ณ , ๊ฐ๋จํ๊ฒ ์ฌ์ฉํด๋ณด๊ฒ ๋ค.
์ฐ์ Zod์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ์์ฑํ๋ค.
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Schema } from "./Schema";
const ZodComponent = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(Schema),
});
const onSubmit = (data) => {
console.log(data);
};
//์ ํจ์ฑ ๊ฒ์ฆ parse ์ฌ์ฉํด๋ด
Schema.parse({
email: "123123@test.test",
password: "1231231Edd@23",
});
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<h2>ZodComponent</h2>
<input
placeholder="์ด๋ฉ์ผ์ ์
๋ ฅํ์ธ์"
{...register("email")}
/>
{errors.email && <p>{errors.email.message}</p>}
<input
placeholder="๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํ์ธ์"
{...register("password")}
/>
{errors.password && <p>{errors.password.message}</p>}
<button>Submit</button>
</form>
</>
);
};
export default ZodComponent;
์ด๋ ๊ฒ ์์ฑํ ํ ๋ฉ์ธํ์ด์ง์ ZodComponent๋ฅผ import ํ์ฌ ๋ถ๋ฌ์ค๋ฉด,

์ ๊ฐ์ด ๋จ๊ณ , ์์์ด ์ฌ๋ฐ๋ฅด์ง ์๋๋ค๋ฉด

์์์ด ์ฌ๋ฐ๋ฅด๋ค๋ฉด

์๋ฌ ๋ฉ์ธ์ง๊ฐ ์ฌ๋ผ์ง๋๊ฒ์ ๋ณผ์ ์๋ค.
Yup ๊ณผ ๋๊ฐ๋ค.
์ด ์ธ์๋ Zod ์๋ ๋ค์ํ ๋ฉ์๋๋ค๊ณผ ์์๋ค์ด ์๋ค.
๊ฐ๋จํ๊ฒ ์์๋ณด์.
๐งParse ๋ฉ์๋
Zod์๋ ์ ํจ์ฑ ๊ฒ์ฆ์ ํ ์ ์๋ ๋ฉ์๋๊ฐ ์๋ค.
Schema.parse({ email: "123123test.test", password: "1231231Edd@23", });์ด๋ ๊ฒ ์์ฑํ์๋ email์ ์คํค๋ง๊ฐ ํด๋น ํ์์ด ์๋๋ฏ๋ก localhost ์๋ error๊ฐ ๋ฌ๋ค.
๊ทผ๋ฐ ํ์์ ๋ง์ถฐ์ ์์ฑํ๋ฉดSchema.parse({ email: "123123@test.test", password: "1231231Edd@23", });์๋ฌ๋ ์ฌ๋ผ์ง๊ณ ํ๋ฉด์ ์ ๋ฌ๋ค.
๐งsafeParse ๋ฉ์๋
parse ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ์ค๋ฅ๋ฅผ ๋ฐ์ํ๊ฒ ํ๋ค๋ฉด,
safeParse ๋ฉ์๋๋ ๊ฒ์ฆ ์คํจ์ Zod๊ฐ ์ค๋ฅ๋ฅผ ๋ฐ์ํ์ง ์๊ฒ ํด์ฃผ๋ ๋์ ๊ฒ์ฆ๋ฌธ์ ์ ๋ํด ์ ๋ณด๊ฐ ํฌํจ๋ ZodError์ธ์คํด์ค๋ฅผ ํฌํจํ๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.const data = { email: "123123test.test", password: "1231231Edd@23", }; const temp = Schema.safeParse(data); if (!temp.success) { console.error(temp.error); }์ด๋ ๊ฒ ์์ฑํ๋ฉด ,
์ ์ฌ์ง๊ณผ ๊ฐ์ด ์ฝ์์ฐฝ์ zodError๊ฐ ๋จ๊ณ , ํ์์ ๋ง์ถฐ ์์ฑํ๋ฉด ์ฌ๋ผ์ง๋ค.
๐งZod์ ๋ค์ํ ์์๋ค
- .Shape
.shapeํน์ ํค์ ๋ํ ์คํค๋ง์ ์ก์ธ์คํ๋ ๋ฐ ์ฌ์ฉ- .passthrough
์คํค๋ง ๊ตฌ๋ฌธ ๋ถ์ ์ค ์ธ์ ํ ์ ์๋ ํค๋ฅผ ํต๊ณผํจ- .strict
์ ๋ ฅ์ ์ ์ ์๋ ํค๊ฐ ์์ผ๋ฉด Zod๋ ์ค๋ฅ๋ฅผ ๋ฐ์์ํด- .extend
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ฒด ์คํค๋ง์ ์ถ๊ฐ ํ๋๋ฅผ ์ถ๊ฐํ ์ ์์
Yup ์ ๊ณต๋ถํ๋ฉด์ Zod ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๊ฐ์ ๊ธฐ๋ฅ์ ํ๋ค๋๊ฒ์ ์๊ฒ๋์๊ณ ,
์ด๋ฒ ์ฃผ์ ๋ "Zod ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํํค์น๊ธฐ" ๋ก ์ก์๋ค.
๊พธ๋ฏธ๋๊ฑฐ๋ ๋ค ์๋ตํ๊ณ ์์ฃผ ๊ฐ๋จํ๊ฒ ๋ง๋ค์ด๋ดค๋๋ฐ, ๊ณต์๋ฌธ์๋ฅผ ๋ค ์ฐธ๊ณ ํ๊ณ ์๋ฌ๊ฐ ์ด๋ป๊ฒ ๋จ๋์ง ๋ค ์ฌ์ฉํด๋ณด๋ฉด์ ๋ง๋ค์ด๋ณด๋ ์๊ฐ์ ๋ง์ด ์์๋์ง๋ง ์ ์ดํด๋๋๊ฑฐ ๊ฐ๋ค.
์ํผ ์ง ์ข์ ๊ณต๋ถ๊ฐ ๋๊ฒ๊ฐ์ ๋ฟ๋ฏ -โจ