최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday

티스토리 뷰

Zustand

- reduxjs/toolkit 이 많이 편해지긴 했지만, 여전히 다루기 거대하고 복잡하다
   포스팅 게제 현시점 (2024.12.24) zustand의 인기와 사용량이 상당히 증가하였다

 

- 1위가 reduxjs/toolkit 인 것은 여전하다, 그렇지만 zustand가 거의 동일한 수준으로 인지도가 높아졌다

 

- jotai 는 3순위 정도 되며, 4순위 recoil 과, 5순위 valtio(어려워 보인다...) 는 상대적으로 1, 2 순위와 비교하였을 때

  그 사용량이 매우 저조하다


   

- zutand는 Redux와 매우 닮아 있으며, 간단하게는 메서드 방식과 프로젝트 규모와 소스코드가 길어지는 경우

  redux처럼 리듀서 방식으로 사용할 수 있다.

 

zustand 의 메서드 방식

장점

  • 간단함: 불필요한 액션 타입이나 리듀서를 정의하지 않아도 되므로 코드가 간결하고 직관적임.
  • 빠른 구현: 단순한 상태 관리에서는 빠르게 구현 가능.
  • 가독성: 함수 이름(increment, decrement)이 명확하여 상태를 변경하는 로직이 직관적임.

단점

  • 확장성 부족: 상태와 액션이 많아질 경우, 모든 로직을 한 파일에 유지해야 하므로 관리가 어려워질 수 있음.
  • 테스트 어려움: 각 함수의 로직을 독립적으로 테스트하기 힘듦(리듀서 방식보다 상대적으로).

사용 사례

  • 상태가 단순하고 액션이 많지 않은 소규모 프로젝트에서 적합.
  • 간단한 상태 업데이트가 주된 요구사항일 때 선호.

 

src/stores/methodCountStore.ts

import { create } from "zustand";

type State = {
  count: number;
};

type Actions = {
  increment: (qty: number) => void;
  decrement: (qty: number) => void;
};

export const useMethodCountStore = create<State & Actions>((set) => ({
  count: 0,
  increment: (qty: number) => set((state) => ({ count: state.count + qty })),
  decrement: (qty: number) => set((state) => ({ count: state.count - qty })),
}));

 

src/components/MethodCounter.tsx

import React from "react";
import { useMethodCountStore } from "../stores/methodCountStore";

const MethodCounter = () => {
  const { count, increment, decrement } = useMethodCountStore();

  return (
    <div>
      <h1>Method Count: {count}</h1>
      <button onClick={() => increment(1)}>+1</button>
      <button onClick={() => decrement(1)}>-1</button>
    </div>
  );
};

export default MethodCounter;

 

src/App.tsx

import "./styles.css";
import MethodCounter from "./components/MethodCounter";

export default function App() {
  return (
    <div className="App">
      <MethodCounter />
    </div>
  );
}

 

zustand 의 리듀서 방식

장점

  • 유사성: Redux와 같은 리듀서 기반 상태 관리 방식과 유사하므로 Redux 경험이 있는 개발자들이 쉽게 이해하고 적응할 수 있음.
  • 확장성: 액션이 많아질 경우, 리듀서를 별도 파일로 분리하여 상태 관리와 비즈니스 로직을 모듈화할 수 있음.
  • 테스트 용이성: 리듀서 로직을 독립적으로 테스트하기 쉬움.

단점

  • 간단한 상태 관리에 불필요한 복잡성: 리듀서 함수와 액션 타입을 정의해야 하므로 초기 설정이 비교적 번거로움.
  • 가독성 저하: 코드의 길이가 길어지고, 디스패치를 사용하는 방식이 직관적이지 않을 수 있음.

사용 사례

  • 상태가 복잡하고 액션이 많은 대규모 프로젝트에서 적합.
  • 상태 관리 로직이 단순하지 않은 경우(다양한 액션 타입과 비즈니스 로직이 필요한 경우) 선호.

src/stores/countStore.ts

import { create } from "zustand";

type State = {
  count: number;
};

type Actions = {
  increment: (qty: number) => void;
  decrement: (qty: number) => void;
};

type Action = {
  type: keyof Actions;
  qty: number;
};

type Dispatch = {
  dispatch: (action: Action) => void;
};

const countReducer = (state: State, action: Action): State => {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.qty };
    case "decrement":
      return { count: state.count - action.qty };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
};

export const useCountStore = create<State & Dispatch>((set) => ({
  count: 0,
  dispatch: (action: Action) => set((state) => countReducer(state, action)),
}));

 

src/components/Counter.tsx

import React from "react";
import { useCountStore } from "../stores/countStore";

const Counter = () => {
  const { count, dispatch } = useCountStore();

  const handleIncrement = () => {
    dispatch({ type: "increment", qty: 1 });
  };

  const handleDecrement = () => {
    dispatch({ type: "decrement", qty: 1 });
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={handleIncrement}>+1</button>
      <button onClick={handleDecrement}>-1</button>
    </div>
  );
};

export default Counter;

 

src/App.tsx

import "./styles.css";
import Counter from "./components/Counter";

export default function App() {
  return (
    <div className="App">
      <Counter />
    </div>
  );
}

 

결론

  • 초기 상태가 단순하고 액션이 몇 개 없다면, 메서드 방식을 사용
  • 복잡한 상태 관리나 상태 변경 로직이 많아질 경우, 리듀서 방식으로 전환하는 것이 좋을듯
  • 프로젝트 초기에는 간단한 방식으로 시작하고, 복잡성이 증가하면 리듀서 방식으로 리팩터링하는 것도 좋은 접근일 듯 하다

 

위의 예제 실제 코드 보기

댓글