ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 간단하면서도 강력한 React 상태관리 라이브러리, Zustand
    IT탐구생활 2025. 2. 13. 07:58
    반응형

    React 애플리케이션에서 규모가 커짐에 따라 여러 컴포넌트 간 상태 공유가 필수가 되었습니다. Redux, MobX, Recoil 등 다양한 라이브러리들이 이런 요구를 해결해주고 있는데요. 최근에는 Zustand라는 가벼운 상태관리 라이브러리가 주목받고 있습니다.

    본 글에서는 Zustand가 무엇이고, 어떤 장점을 가지고 있으며, 어떻게 사용하면 좋은지 간단한 예시와 함께 살펴보겠습니다.


    1. Zustand란?

    Zustand는 독일어로 “상태(state)”라는 의미이며, 단순하고 직관적인 API를 제공하는 React 상태관리 라이브러리입니다.
    Zustand는 최소한의 보일러플레이트로도 전역 상태를 다룰 수 있게 해주어, 작은 프로젝트나 빠른 프로토타이핑에서뿐만 아니라, 규모가 큰 프로젝트에서도 부담 없이 도입할 수 있다는 장점이 있습니다.

    주요 특징

    1. 가벼움: 복잡한 설정이나 보일러플레이트 코드를 최소화해 비교적 쉽게 도입 가능
    2. React Hooks 기반: 함수형 컴포넌트에서 손쉽게 전역 상태를 읽고, 변경할 수 있음
    3. Immer 지원: 상태 변경 시 불변성 관리를 자동으로 도와주는 Immer를 쉽게 연동 가능
    4. Redux DevTools 호환: 코드를 조금만 추가하면 Redux DevTools를 사용할 수 있어 디버깅이 편리

    2. 설치 방법

    Zustand를 프로젝트에 추가하려면, 아래 명령어를 사용하세요.

    # npm
    npm install zustand
    
    # yarn
    yarn add zustand

    이 후, React 환경에서 import해 사용하면 됩니다.


    3. 기본 사용 예시

    Zustand의 핵심 아이디어는 “store(상태 저장소)를 생성하고, 해당 스토어에서 필요한 상태와 업데이트 로직을 정의한 뒤, React 컴포넌트에서 훅으로 불러와 사용하는 것”입니다.

    1) 스토어 생성하기

    예를 들어 카운터 기능을 위한 상태 관리를 zustand로 구성해보겠습니다.

    // counterStore.js
    import create from 'zustand';
    
    // 1. create() 함수를 이용해 스토어를 생성합니다.
    export const useCounterStore = create((set) => ({
      count: 0,
      increase: () => set((state) => ({ count: state.count + 1 })),
      decrease: () => set((state) => ({ count: state.count - 1 })),
    }));
    • create() : Zustand가 제공하는 핵심 함수로, 전역 상태와 액션(메서드)을 정의하는 객체를 반환합니다.
    • set : 현재 상태를 업데이트하는 함수. 기존 상태 state를 인자로 받아 새로운 상태를 반환하거나, 업데이트 방법을 객체 형태로 전달할 수 있습니다.

    2) React 컴포넌트에서 사용하기

    만들어둔 useCounterStore 훅을 React 컴포넌트에서 불러와서, 카운터 UI를 구현해봅시다.

    // CounterComponent.jsx
    import React from 'react';
    import { useCounterStore } from './counterStore';
    
    function CounterComponent() {
      // 1. 훅을 사용해 상태와 함수를 받아옵니다.
      const { count, increase, decrease } = useCounterStore();
    
      return (
        <div>
          <h1>{count}</h1>
          <button onClick={increase}>+</button>
          <button onClick={decrease}>-</button>
        </div>
      );
    }
    
    export default CounterComponent;

    이렇게 하면 CounterComponent 어디서든 전역 스토어를 쉽게 참조할 수 있고, 상태가 변경될 때마다 해당 컴포넌트만 리렌더링되므로, 불필요한 렌더링도 방지할 수 있습니다.


    4. 상태 분리(Selector) 사용하기

    Zustand는 “상태 셀렉터(Selector)”를 사용할 수 있도록 지원합니다.
    이는 부분 상태만 선택적으로 구독할 수 있어, 상태 변경 범위를 최소화하고 성능을 최적화하는 데 도움이 됩니다.

    // 일부 상태만 사용 예시
    import React from 'react';
    import { useCounterStore } from './counterStore';
    
    function SomeComponent() {
      // count 값만 구독
      const count = useCounterStore((state) => state.count);
    
      // increase 함수만 구독
      const increase = useCounterStore((state) => state.increase);
    
      return (
        <div>
          <h1>Count: {count}</h1>
          <button onClick={increase}>Increment</button>
        </div>
      );
    }

    상태가 매우 많아지는 대규모 스토어에서도 필요한 속성만 골라 구독하면, 업데이트 시 컴포넌트가 불필요하게 리렌더링되는 현상을 줄일 수 있습니다.


    5. Redux DevTools 연동

    디버깅을 돕는 Redux DevTools 확장을 Zustand에서도 손쉽게 사용할 수 있습니다.
    Zustand 설정 시 미들웨어를 적용하는 형태로 간단히 설정할 수 있으며, Redux DevTools가 제공하는 액션 추적, 상태 이력 확인 같은 기능들을 그대로 활용 가능합니다.

    import create from 'zustand';
    import { devtools } from 'zustand/middleware';
    
    export const useCounterStore = create(
      devtools((set) => ({
        count: 0,
        increase: () => set((state) => ({ count: state.count + 1 })),
        decrease: () => set((state) => ({ count: state.count - 1 })),
      }), { name: 'CounterStore' }) // DevTools에서 표시될 스토어 이름
    );

    이후 브라우저의 Redux DevTools를 열면, “CounterStore”라는 이름으로 상태 변경 이력을 추적할 수 있습니다.


    6. Immer 연동

    Zustand 자체도 내부적으로 Immer를 사용하거나, 필요에 따라 미들웨어로 쉽게 연동할 수 있습니다.
    이를 통해 불변성(Immutable)을 간편하게 유지하면서 객체 또는 배열 업데이트 로직을 작성할 수 있습니다.

     
    import create from 'zustand';
    import { immer } from 'zustand/middleware/immer';
    
    export const useTodoStore = create(
      immer((set) => ({
        todos: [],
        addTodo: (todo) =>
          set((state) => {
            state.todos.push(todo); // Immer 덕에 이렇게 직접 푸시 가능
          }),
      }))
    );

    7. 장단점 정리

    장점

    1. 간결한 API
      • create() 함수 하나만 기억해도 되며, 필요한 상태와 메서드를 객체로 정의하면 전역 상태관리가 끝납니다.
    2. 선택적 구독(Selector)
      • 컴포넌트에서 필요한 데이터만 구독 가능해, 불필요한 리렌더링이 줄어듭니다.
    3. 심플하면서 확장성 있음
      • Redux DevTools, Immer 등 미들웨어 연동으로 기능을 쉽게 확장할 수 있습니다.

    단점

    1. 기존에 Redux 사용 경험이 없으면 낯설 수 있음
      • React만 써본 분들에게는 크게 문제 없지만, Redux 패턴에 익숙한 분이라면 처음엔 “어, reducer나 action 타입이 어디 있지?” 하고 어색할 수 있습니다.
    2. 아직은 커뮤니티가 Redux만큼 크지 않음
      • Zustand가 인기 있어지고 있지만, Redux나 MobX만큼 오랜 사용자층을 가진 건 아니어서 에코시스템이 조금 작을 수 있습니다.
    3. 복잡한 비즈니스 로직
      • 프로젝트가 매우 커지면 명확한 설계 패턴(Redux 스타일의 분산된 reducer, action 구조)을 선호할 수도 있습니다. (단, Zustand를 여러 개의 스토어로 쪼개어 운영하면 충분히 커버 가능)

    8. 결론

    Zustand는 “최소한의 코드로 React 애플리케이션 전역 상태를 관리하고 싶다”는 요구에 잘 부합하는 라이브러리입니다. 특히 복잡도가 크지 않은 프로젝트, 빠른 프로토타이핑, 개발 생산성이 중요한 상황에서 손쉽게 도입할 수 있습니다.

    또한 Redux DevTools와 Immer 연동까지 고려하면, “간결함”“확장성”을 모두 잡을 수 있다는 점이 매력 포인트입니다.

    “간단한 코드로 상태를 관리하면서도, 필요하면 미들웨어로 얼마든지 확장할 수 있다!”
    이것이 Zustand가 지향하는 철학이라 볼 수 있습니다.

    리액트 상태관리 선택지가 워낙 다양하다 보니 정답은 없습니다. 다만, “작고 가벼운 라이브러리”, “명확한 구독과 업데이트 흐름”, “손쉬운 미들웨어 통합”이 필요한 프로젝트라면 한 번쯤 Zustand를 시도해 보시는 걸 추천드립니다.

    반응형

    댓글

Designed by Tistory.