상태 관리 라이브러리 -- Zustand

2026. 1. 2. 21:58STUDY

반응형

Zustand란 무엇인가?

Zustand는 발행/구독(Pub/Sub) 모델을 기반으로 하는 작고 빠르며 확장 가능한 상태 관리 라이브러리입니다.
Flux 패턴을 따르되, 보일러플레이트(반복되는 코드)를 최소화하여 개발자 경험을 극대화
    - 간결함: Redux처럼 복잡한 설정이나 Provider로 감싸는 과정이 필요 없습니다.
    - 성능: 상태가 변경될 때 리렌더링을 최적화하는 기능이 내장되어 있습니다.
    - 유연성: 특정 라이브러리에 종속되지 않아 React 없이도 사용할 수 있습니다.

 

✓ 상태 관리 라이브러리란?

프론트엔드 개발에서 상태(State)는 관리해야하는 모든 동적 데이터를 의미한다. (로그인여부, 입력창의 텍스트 등등)

상태관리라이브러리는 이런 데이터들을 컴포넌트 사이에 효율적으로 전달하고, 데이터가 수정된 경우 화면을 업에트하기 위한 도구

 

스토어(Store)의 정의

스토어는 애플리케이션의 중앙 데이터 저장소이자 상태 관리 센터

→ 데이터를 한곳에 모아두고, 아무나 수정하지 못하게 관리하며 데이터가 수정될때마다 필요한 컴포넌트에게 전달

중앙화: 모든 컴포넌트가 부모-자식 관계와 상관없이 직접 스토어에 접근

 

스토어의 3가지 구성 요소
1) 상태 (State / 변수) -- 기억해야 할 실제 데이터
2) 액션 (Action / 메서드) -- 상태를 변경하는 방법
 외부에서 데이터를 직접 수정하는 게 아니라, 반드시 이 '액션'을 통해서만 수정
3) 구독 (Subscription) -- 상태가 바뀌었을 때 화면을 다시 그리도록 연결하는 메커니즘

 

🧩
스토어는 우리 앱의 정보를 안전하게 보관하고 관리하는 중앙 데이터 센터
Zustand는 이 스토어를 매우 가볍고 직관적으로 만들 수 있게 해주는 툴이다.

 

✓ 발행/구독(Pub/Sub) 모델
(유튜브 채널 알림 설정같은 느낌)
- 발행(Publish): 정보(상태)를 가지고 있는 주체(Store)가 데이터가 변경되었음을 알리는 것.
- 구독(Subscribe): 특정 데이터가 궁금한 컴포넌트들이 스토에 '데이터 바뀌면 알려줘' 라고 등록해두는것.
e.g. Store(발행자)는 유저 정보를 가지고 있다 → 마이페이지 컴포넌트(구독자)에서 유저정보를 구독하고 있다. → 유저가 닉네임을 변경하면, 스토어는 닉네임이 바뀐걸 발행하고, 구독하던 마이페이지 컴포넌트는 소식을 듣고 화면을 그림

 

✓ Flux 패턴
Meta(페이스북)이 제안한 [데이터는 무조건 한 방향으로 흐른다] 는 규칙
유저가 직접 store를 수정하는게 아닌, 미리 정의된 login(), logout()같은 Action을 통해서만 상태를 수정 → 데이터 흐름 예측 가능

 

✓ 리렌더링을 최적화
바뀐 데이터와 상관 있는 컴포넌트만 다시 화면에 그리는 것
Zustand는 Selector(선택자) 기능을 통해 최적화를 할수 있다.
e.g. store에 email, name 정보를 가지고 잇는경우,
유저의 이름만 보여주는 컴포넌트는 store의 이메일이 아무리 바껴도 name이 바뀌지 않았으므로 리런더링 되지 않음
const UserName = () => {
  // selector를 사용해 name만 '선택'해서 가져옴
  const name = useAuthStore((state) => state.user?.name);
  return <div>{name}</div>;
};

 

 

핵심 개념 및 옵션

1) create()

상태(State)와 상태를 변경하는 액션(Action)을 정의하는 저장소(Store)를 만들수 있다.

 

2) set() 

상태를 업데이트하는 함수

이전 상태를 인자로 받아 새로운 상태로 병합

더보기

Store 정의 (`useAuthStore.ts`)

import { create } from 'zustand';

// 1. 데이터 모델 정의
interface User {
  id: number;
  name: string;
  nickname: string;
  email: string;
}

// 2. 스토어 상태 및 액션의 인터페이스 정의
interface AuthState {
  user: User | null;
  isLoggedIn: boolean;
  login: (userData: User) => void;
  logout: () => void;
  updateNickname: (newNickname: string) => void;
}

// 3. 순수 Zustand 스토어 생성 (미들웨어가 생략)
export const useAuthStore = create<AuthState>((set) => ({
  // 초기 상태
  user: null,
  isLoggedIn: false,

  // 로그인: 전체 객체를 교체
  login: (userData) => set({ 
    user: userData, 
    isLoggedIn: true 
  }),

  // 로그아웃: 초기값으로 리셋
  logout: () => set({ 
    user: null, 
    isLoggedIn: false 
  }),

  // 닉네임 수정: 현재 상태(state)를 참조하여 일부만 변경
  updateNickname: (newNickname) => set((state) => ({
    user: state.user 
      ? { ...state.user, nickname: newNickname } 
      : null
  })),
}));



 

3) get()

현재 스토어에 저장된 상태를 조회하는 함수

- 액션함수 내부에서 현재 상태를 참조하는 경우

- 비동기 작업 (API호출 등)을 수행할 때, 현재 유저정보나 토큰이 필요한 경우

더보기

get 예시

- 액션을 실행하기 전에 현재 상태를 확인 하는 용도

export const useAuthStore = create<AuthState>((set, get) => ({
  user: null,
  isLoggedIn: false,

  updateUserIfChanged: (newUserData: User) => {
    const currentUser = get().user;
    if (currentUser?.id === newUserData.id) {
      console.log("변경사항이 없어 업데이트를 생략합니다.");
      return;
    }
    set({ user: newUserData });
  }
}));

 

- 여러 상태 값을 조합하여 하나의 결과를 도출

export const useAuthStore = create<AuthState>((set, get) => ({
  user: { name: '홍길동', role: 'ADMIN' },
  isLoggedIn: true,
  checkAdminPrivilege: () => {
    const { user, isLoggedIn } = get(); 
    if (isLoggedIn && user?.role === 'ADMIN') {
      return "관리자 권한이 확인되었습니다.";
    }
    return "권한이 없습니다.";
  }
}));

 

 

4) Middleware 미들웨어

• persist: localStorage나 sessionStorage에 자동으로 저장하여 새로고침해도 데이터가 유지 

• devtools: 크롬 개발자 도구에서 상태변화를 모니터링 

  → 실제 배포 시, 개발환경에서만 활성화 되도록 설정하거나 그대로 두어도 성능상 큰 지장을 주지 않지만, 디버깅용으로는 필수

 

✓ 브라우저 저장소: localStorage vs sessionStorage
구분 localStorage sessionStorage
유지기간 브라우저를 닫아도 영구적으로 유지 탭이나 창이 닫으면 즉시 삭제
공유범위 같은 도메인의 모든 창/탭 현재 탭 내부에서만 유지 (다른탭과 공유 불가)
용량 약 5MB~10MB 15MB
주요 용도 자동 로그인 토큰, 사용자 설정(다크모드) 일회성 입력폼데이터, 비회원 장바구니

 

컴포넌트에서 store 사용 예시

로그인 컴포넌트 (LoginComponent.tsx)

더보기
import React from 'react';
import { useAuthStore } from './useAuthStore';

export default funciton LoginComponent(){
  // 스토어에서 필요한 상태와 함수를 가져옴
  const { user, isLoggedIn, login, logout, updateNickname } = useAuthStore();

  const handleLogin = () => {
    // 실제 환경에서는 API 호출 결과값을 넣게 됨
    const mockUser = {
      id: 1,
      name: '홍길동',
      nickname: '길동이',
      email: 'gildong@example.com'
    };
    login(mockUser);
  };

  const handleChangeNickname = (e: React.ChangeEvent<HTMLInputElement>) => {
    updateNickname(e.target.value);
  };

  if (!isLoggedIn || !user) {
    return (
      <div>
        <p>로그인이 필요합니다.</p>
        <button onClick={handleLogin}> 로그인 테스트 </button>
      </div>
    );
  }

  return (
    <div>
      <h2>환영합니다, {user.name}님!</h2>
      <p>현재 닉네임: {user.nickname}</p>
      <div className="mt-4 space-y-2">
        <input type="text" placeholder="새 닉네임 입력" onChange={handleChangeNickname}/>
        <button onClick={logout}>로그아웃</button>
      </div>
    </div>
  );
};

 


💡나는 주로 어떻게 사용했지? 

지금 하고 개발하고 있는 프로젝트에선 useUserStore.ts와 useLoadingStore.ts 두 개의 스토어를 먼저 구축하여 관리하고 있습니다.

- useUserStore.ts: 유저 상태 및 정보
- useLoadingStore.ts: 페이지, API 호출, 액션 단위의 전역 로딩 상태 관리

아직 진행중이기 때문에 어떤 파일이 더 생겨날진 봐야겠지만 :-)

리액트 배우면서 zustand를 처음사용했고 지금도 사용하고 있기때문에 다른 라이브러리랑 비교하긴 어렵지만, 로직이 간단해서 만족하며 사용하고 있습니다.

 

_

Nextjs로 프로젝트를 진행할때,

"서버에서 가져온 데이터를 어떻게 클라이언트 스토어에 안전하게 동기화(Hydration)할 것인가?" 였습니다.

그 부분은 다음 포스팅에서 작성해야겠습니다. 🤯

 

Zustand와 Next.js, 안전한 데이터 동기화(Hydration)

Next.js의 서버 사이드 렌더링(SSR) 환경으로 넘어오면 한 가지 고민이 생깁니다."서버에서 받아온 데이터를 어떻게 클라이언트의 Zustand 스토어에 안전하게 전달할 것인가?"useEffect로 넣기엔 깜빡임

radan.tistory.com

 

 

 

💻 공식문서 확인

https://zustand.docs.pmnd.rs/getting-started/introduction

 

Introduction - Zustand

How to use Zustand

zustand.docs.pmnd.rs

 

 

 

 

 

 

 

 

반응형