๐ŸงธZod ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ JavaScript๋กœ ์‚ฌ์šฉํ•˜๋Š”๋ฒ•

์ตœํ•˜์˜ยท2024๋…„ 4์›” 5์ผ

Library

๋ชฉ๋ก ๋ณด๊ธฐ
1/1
post-thumbnail

๐ŸงธZod๋ž€ ??

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 ์‚ฌ์šฉํ•˜๊ธฐ์ „ ์„ค์น˜ํ•ด์•ผํ• ๊ฒƒ๋“ค!

  • 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๋ฅผ ์‚ฌ์šฉํ•˜์…จ๊ธธ๋ž˜ ๋‚˜๋Š” ๊ฟ‹๊ฟ‹ํ•˜๊ฒŒ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋กœ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ๋‹ค.

๐ŸงธZod ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ๋ฒ•

โš™๏ธSchema ์„ค์ •

์šฐ์„  ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๋ ค๋ฉด ์ž…๋ ฅ ํผ์— ๋Œ€ํ•œ ์Šคํ‚ค๋งˆ๋ฅผ ์ž‘์„ฑํ•ด์ค˜์•ผํ•œ๋‹ค.
(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์€ ์œ ํšจํ•˜์ง€ ์•Š์€ ์ˆซ์ž ๊ฐ’์ด์ง€๋งŒ ์„ ํƒ์ ์ด๋ฏ€๋กœ ์ „์ฒด ์Šคํ‚ค๋งˆ๋Š” ์—ฌ์ „ํžˆ ์œ ํšจ.

โ—โœ‹๊ฐ™์€ Schema์ธ Yup ์ด๋ž‘ ์ฝ”๋“œ๊ฐ€ ๋ญ๊ฐ€ ๋‹ค๋ฅผ๊นŒ?!

๋น„๊ต๋ฅผ ์œ„ํ•ด ์•„๋ž˜๋Š” 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;

โš™๏ธZodComponent ์ƒ์„ฑ

์œ„์™€ ๊ฐ™์ด ์Šคํ‚ค๋งˆ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค๋ฉด ์ด์ œ ์ง์ ‘ ์จ๋ณผ ์ฐจ๋ก€์ด๋‹ค.
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 ์—๋Š” ๋‹ค์–‘ํ•œ ๋ฉ”์†Œ๋“œ๋“ค๊ณผ ์š”์†Œ๋“ค์ด ์žˆ๋‹ค.
๊ฐ„๋‹จํ•˜๊ฒŒ ์•Œ์•„๋ณด์ž.

โš™๏ธ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 ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํŒŒํ—ค์น˜๊ธฐ" ๋กœ ์žก์•˜๋‹ค.
๊พธ๋ฏธ๋Š”๊ฑฐ๋Š” ๋‹ค ์ƒ๋žตํ•˜๊ณ  ์•„์ฃผ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค์–ด๋ดค๋Š”๋ฐ, ๊ณต์‹๋ฌธ์„œ๋ฅผ ๋‹ค ์ฐธ๊ณ ํ•˜๊ณ  ์—๋Ÿฌ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋œจ๋Š”์ง€ ๋‹ค ์‚ฌ์šฉํ•ด๋ณด๋ฉด์„œ ๋งŒ๋“ค์–ด๋ณด๋‹ˆ ์‹œ๊ฐ„์€ ๋งŽ์ด ์†Œ์š”๋์ง€๋งŒ ์ž˜ ์ดํ•ด๋๋˜๊ฑฐ ๊ฐ™๋‹ค.
์•”ํŠผ ์งˆ ์ข‹์€ ๊ณต๋ถ€๊ฐ€ ๋œ๊ฒƒ๊ฐ™์•„ ๋ฟŒ๋“ฏ -โœจ

0๊ฐœ์˜ ๋Œ“๊ธ€