Z모음 : Zustand & Zod

송윤서·2025년 3월 29일

Next.js

목록 보기
6/7
post-thumbnail

상태 관리 도구, 뭐 써야 할지 고민된다면?
오늘은 내가 최근에 써본 Zustand에 대해 소개해보려고 한다.
왜 Zustand를 쓰는지, 기존 상태 관리 도구들과 비교했을 때 어떤 장점이 있는지, 그리고 기본적인 사용법까지 함께 정리해보겠다. 그러면 바로 시작!!

zustand

독일어로 상태라는 뜻을 가진 상태 관리 라이브러리이다.

장점

  1. 쏘이지하다
    -> 동작을 이해하기 위해 알아야하는 코드 양이 적다. (핵심 로직의 코드 줄이 약 42줄이 끝이다. VanillaJS 기준)
  2. 보일러플레이트가 거의 없다
  3. Redux Develos 사용이 가능해 devugging에 용이하다.
  4. 상태 변경 시 불필요한 리렌더링을 일으키지 않도록 제어하기가 쉽다.

이 외에도 다양한 장점들이 존재합니다.

동작 원리

컴포넌트는 store를 구독(subscribe)해서 필요한 상태만 받아오고 해당 값이 바뀔 때만 리렌더링이 발생한다.

  • create() 함수를 통해 store를 생성하고 내부적으로는 클로저를 이용해 상태 기억
  • set, get 등의 내부 메서드로 상태를 읽고 수정하며
    React 컴포넌트에서는 useStore((state) => state.원하는값) 방식으로 접근
  • 필요하면 Redux DevTools와도 연동 가능

-> 덕분에 불필요한 렌더링을 줄이고, 간단한 코드로 글로벌 상태 관리를 구현할 수 있다!

useShallow으로 불필요한 리렌더링 방지하기

❓ 왜 써야 할까?

useStore((state) => ({ ... })) 형태로 객체 여러 값을 가져오면, 참조가 바뀌어 매번 리렌더링이 발생할 수 있다. 그래서 이걸 해결하려면?

// 해결 방법 -> useShallow
import { useShallow } from "zustand/react/shallow";

const { count, increment, decrement } = useTestStore(
  useShallow((state) => ({
    count: state.count,
    increment: state.increment,
    decrement: state.decrement,
  }))
);

사용법

1. 설치하기

// npm <- pnpm은 그냥 앞에 p하나만 붙여주면 된다.
npm add zustand

// yarn 설치
$ yarn add zustand

2. Create Store

store란? 애플리케이션의 여러 상태(State)를 중앙에서 관리하는 패턴

// 전역 Store 생성 및 활용
import React from "react";
import { create } from "zustand";

type Props = {};

interface TestStore {
  count: number;
  increment: () => void;
  decrement: () => void;
}

const useTestStore = create<TestStore>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

const ZustandTestpage = (props: Props) => {
  return <div>ZustandTestpage</div>;
};

export default ZustandTestpage;

3. store에 생성한 useStore를 불러와서 사용

import useStore from '../store.ts'

const ZustandTestpage = (props: Props) => {
  const { count, increment, decrement } = useTestStore();
  //   const count = useTestStore((state) => state.count);
  //   const count = useTestStore(useShallow((state) => state.count));

  return (
    <div>
      <div>{count}</div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
};

export default ZustandTestpage;

Zod 라이브러리

스키마 선언 및 유효성 검사 라이브러리

  • Zod를 React 에서 사용할 땐 react-hook-form과 같이 사용하면 좋다고한다.

사용이유

필요한 이유가 TypeScript의 한계 때문?
TypeScript는 타입을 엄격하게 관리해주긴 하지만, 어디까지나 컴파일 시점에서만 가능하다.
실제로 코드를 실행하는 건 JavaScript이기 때문에, 런타임에서의 타입 에러는 막을 수 없다는 한계가 있다.

게다가 TypeScript는 number 타입까진 제한할 수 있어도,
특정 범위의 숫자나 특정 문자열만 허용하는 식의 정교한 제약은 어렵고, 정수/실수 구분도 불가능하다.

그래서 이런 부족한 부분을 보완해주는 게 바로 Zod 같은 런타임 타입 검증 라이브러리다.

주요 특징

  • 의존성 없음 : 외부 라이브러리에 의존하지 않습니다.
  • 범용성 : Node.js와 모든 최신 브라우저에서 동작합니다.
  • 경량화 : 압축 후 8KB로 매우 작습니다.
  • 불변성 : .optional()과 같은 메서드는 새로운 인스턴스를 반환합니다.
  • 간결한 체인 인터페이스 : 메서드 체이닝을 통해 코드를 간결하게 작성할 수 있습니다.
  • 함수형 접근 : "검증하지 말고 파싱하라" 철학을 따릅니다.
  • JavaScript 지원 : TypeScript를 사용하지 않아도 됩니다. 일반 JavaScript에서도 동작합니다.

사용법

1. 설치하기

pnpm add zod    

pnpm add react-hook-form zod @hookform/resolvers

2.스키마 정의.

스키마란? 쉽게 말해서 데이터의 형태와 구조를 뜻한다.

// 예시
const schema = z.object({
  name: z.string().min(3),
  email: z.string().email(),
  age: z.number(),
});

3. 유효성 검사

Zod 스키마를 정의하고 나면, 두 종류(parse, safeParse)의 스키마 함수를 통해 유효성 검사를 할 수 있다. 함수에 검증하고 싶은 값을 넘겨서 호출하면 검증 결과를 확인할 수 있다.

// 예시
schema.parse({
	name: "jieun",
	email: "email@email.com",
	age: 20,
});  // 통과

schema.parse({
	name: "jieun",
	email: "email@email.com",
});  // 검증 실패

parse vs safeParse

parse
: 오류가 발생하는 경우 오류 메시지와 함께 throw 함수를 호출해 서버가 중단된다.
safeParse
: 오류가 발생하는 경우, 서버 중단 없이 결과 객체에 오류 메시지 및 여러 정보를 전달하고, 개발자는 이 객체에서 프로퍼티를 추출해 검증 로직을 작성할 수 있다.

예시

"use client";

import React from "react";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

type Props = {};

const ZodTestpage = (props: Props) => {
  const schema = z.object({
    name: z.string().min(10, "이름은 16글자 이상으로"),
    email: z.string().email("이메일이 아닌 것 같아"), // 에러 메시지 작성 가능함
  });
  const form = useForm<z.infer<typeof schema>>({
    resolver: zodResolver(schema),
    defaultValues: {
      name: "",
      email: "",
    },
  });

  const onSubmit = (values: z.infer<typeof schema>) => {
    console.log(values);
  };
  return (
    <div>
      <form
        onSubmit={form.handleSubmit(onSubmit)}
        className="container mx-auto flex flex-col gap-3"
      >
        <input
          type="text"
          {...form.register("name")}
          className="border-2 border-gray-300 rounded-md p-2"
        />
        {form.formState.errors.name && (
          <p className="text-red-500">{form.formState.errors.name.message}</p>
        )}
        <input
          type="text"
          {...form.register("email")}
          className="border-2 border-gray-300 rounded-md p-2"
        />
        {form.formState.errors.email && (
          <p className="text-red-500">{form.formState.errors.email.message}</p>
        )}
        <button type="submit" className="bg-blue-500 rounded-md p-2">
          submit
        </button>
      </form>
    </div>
  );
};

export default ZodTestpage;

실행화면

마무리

이번 글에서는 Zustand와 Zod에 대해 간단히 알아보았다. 두 라이브러리 모두 사용법이 간단하면서도 실용성이 높아, 작은 사이드 프로젝트는 물론 실무에서도 충분히 활용할 수 있다.
부담 갖지 말고 가볍게 써보면서 익숙해져 보는 것도 좋을 것 같다.

profile
Front-end Developer

0개의 댓글