๐Ÿ› Zustand vs Redux

์ž„๊ด‘ํ›ˆยท2025๋…„ 2์›” 3์ผ

npmtrends๋กœ ๋ณธ 5๋…„๊ฐ„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋‹ค์šด๋กœ๋“œ ์ˆ˜

Zustand์™€ Redux๋Š” ๋‘˜ ๋‹ค React์—์„œ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ง€๋งŒ, ์„ค๊ณ„ ๋ฐฉ์‹๊ณผ ์‚ฌ์šฉ์„ฑ์ด ๊ฝค ๋‹ค๋ฅด๋‹ค.

1๏ธโƒฃ Redux vs Zustand ์ฃผ์š” ์ฐจ์ด์ 

ํŠน์ง•ReduxZustand
์„ค์ • ๋ณต์žก๋„โœ… ๋น„๊ต์  ๋ณต์žก (Boilerplate ์ฝ”๋“œ ๋งŽ์Œ)๐Ÿš€ ๊ฐ„๋‹จ (๊ธฐ๋ณธ ํ•จ์ˆ˜๋งŒ์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
Reducer ํ•„์š” ์—ฌ๋ถ€โœ… reducer ํ•„์ˆ˜โŒ ๋ถˆํ•„์š”
Action & Dispatch ๋ฐฉ์‹โœ… dispatch(action)์„ ํ†ตํ•ด ์ƒํƒœ ๋ณ€๊ฒฝ๐Ÿ›  ์ง์ ‘ ์ƒํƒœ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ (mutate ๋ฐฉ์‹)
Middleware ์ง€์›โœ… ๊ฐ•๋ ฅํ•œ Middleware (Redux Thunk, Saga ๋“ฑ)โœ… Middleware ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ๊ธฐ๋ณธ ์ œ๊ณต X
๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๐Ÿ”„ Redux Thunk / Redux Saga ํ•„์š”๐Ÿš€ ์ง์ ‘ ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
๋ฆฌ์•กํŠธ ์ข…์† ์—ฌ๋ถ€๐Ÿ”— React์— ์ข…์†์ ์ด์ง€ ์•Š์Œ (Vanilla JS์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)๐Ÿ”— React ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๊ณ„๋จ
์‚ฌ์šฉ์ž ๊ฒฝํ—˜ (DX)๐Ÿ“œ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ๋งŽ์Œ๐Ÿ”ฅ ์ง๊ด€์ ์ด๊ณ  ๊ฐ„๊ฒฐ
๋ฉ”๋ชจ๋ฆฌ ์„ฑ๋Šฅโณ ์•ฝ๊ฐ„ ๋ฌด๊ฑฐ์›€ (Immer + ๊นŠ์€ ๋ณต์‚ฌ ์‚ฌ์šฉ)โšก ๊ฐ€๋ฒผ์›€ (shallow copy ์‚ฌ์šฉ)

2๏ธโƒฃ Redux๋ž€?

Redux๋Š” Flux ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹ค.

๐Ÿ”น ํ•ต์‹ฌ ๊ฐœ๋…

  1. Store: ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ณณ
  2. Actions: ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•œ ๋ช…๋ น
  3. Reducers: ํ˜„์žฌ ์ƒํƒœ์™€ action์„ ๋ฐ›์•„ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜
  4. Dispatch: action์„ store์— ์ „๋‹ฌํ•˜๋Š” ํ•จ์ˆ˜
  5. Selector: store์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜

โœ… Redux ์‚ฌ์šฉ ์˜ˆ์ œ

import { createStore } from 'redux';

// 1. ์ดˆ๊ธฐ ์ƒํƒœ
const initialState = { count: 0 };

// 2. ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜
function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

// 3. ์Šคํ† ์–ด ์ƒ์„ฑ
const store = createStore(counterReducer);

// 4. ์ƒํƒœ ๋ณ€๊ฒฝ
store.dispatch({ type: 'INCREMENT' });
console.log(store.getState()); // { count: 1 }

Redux๋Š” dispatch(action)์„ ํ˜ธ์ถœํ•ด์•ผ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๊ณ , ์ƒํƒœ ๋ณ€๊ฒฝ์ด ํ•ญ์ƒ ๋ถˆ๋ณ€์„ฑ(immutability) ์„ ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค.

3๏ธโƒฃ Zustand๋ž€?

Zustand๋Š” Redux๋ณด๋‹ค ๋” ์ง๊ด€์ ์ด๊ณ  ๊ฐ„๊ฒฐํ•œ API๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹ค.

โœ… ํŠน์ง•

  • React Hooks ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘
  • Boilerplate ์ฝ”๋“œ๊ฐ€ ๊ฑฐ์˜ ์—†์Œ
  • Redux์ฒ˜๋Ÿผ action๊ณผ dispatch ์—†์ด ์ง์ ‘ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ
  • useStore ํ›…์„ ์‚ฌ์šฉํ•ด ๊ฐ„ํŽธํ•˜๊ฒŒ ์ƒํƒœ๋ฅผ ๊ตฌ๋…

โœ… Zustand ์‚ฌ์šฉ ์˜ˆ์ œ

import { create } from 'zustand';

// 1. Zustand ์Šคํ† ์–ด ์ƒ์„ฑ
const useCounterStore = create((set) => ({
  count: 0,
  increase: () => set((state) => ({ count: state.count + 1 })),
  decrease: () => set((state) => ({ count: state.count - 1 }))
}));

// 2. React ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ
function Counter() {
  const { count, increase, decrease } = useCounterStore();
  
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={increase}>+</button>
      <button onClick={decrease}>-</button>
    </div>
  );
}

useCounterStore() ํ›…์„ ์‚ฌ์šฉํ•ด ์ƒํƒœ๋ฅผ ์‰ฝ๊ฒŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ณ , set()์„ ํ˜ธ์ถœํ•˜๋ฉด ์ง์ ‘ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

4๏ธโƒฃ Redux vs Zustand ๋น„๊ต ์š”์•ฝ

โœ… Redux

โœ”๏ธ ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์— ์ ํ•ฉ
โœ”๏ธ ์ƒํƒœ ๋ณ€๊ฒฝ ํ๋ฆ„์ด ๋ช…ํ™•ํ•˜์ง€๋งŒ, Boilerplate ์ฝ”๋“œ๊ฐ€ ๋งŽ์Œ
โœ”๏ธ Middleware, DevTools ์ง€์›์ด ๊ฐ•๋ ฅ

โœ… Zustand

โœ”๏ธ ์†Œ๊ทœ๋ชจ/์ค‘๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์— ์ ํ•ฉ
โœ”๏ธ ๊ฐ€๋ณ๊ณ  ๊ฐ„๊ฒฐํ•˜๋ฉฐ, Hook ๊ธฐ๋ฐ˜์ด๋ผ ์‚ฌ์šฉํ•˜๊ธฐ ํŽธ๋ฆฌ
โœ”๏ธ ์ƒํƒœ ๋ณ€๊ฒฝ์ด Redux๋ณด๋‹ค ์ง๊ด€์  (์ง์ ‘ set() ํ˜ธ์ถœ ๊ฐ€๋Šฅ)

5๏ธโƒฃ Redux์™€ Zustand ์ด์ „์—๋Š” ์–ด๋–ป๊ฒŒ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ํ–ˆ์„๊นŒ?

React์—์„œ Redux๋‚˜ Zustand ๊ฐ™์€ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋‚˜์˜ค๊ธฐ ์ „์—๋Š” ๋ณดํ†ต Prop Drilling ๋˜๋Š” Context API๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

1. Prop Drilling

๋ถ€๋ชจ โ†’ ์ž์‹ โ†’ ์†์ž ์ปดํฌ๋„ŒํŠธ๋กœ props๋ฅผ ๊ณ„์† ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹.

function Parent() {
  const [count, setCount] = useState(0);
  return <Child count={count} setCount={setCount} />;
}

function Child({ count, setCount }) {
  return <GrandChild count={count} setCount={setCount} />;
}

function GrandChild({ count, setCount }) {
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

๐Ÿšจ ๋ฌธ์ œ์ 

  • ๊นŠ์ด ์ค‘์ฒฉ๋œ ์ปดํฌ๋„ŒํŠธ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์–ด๋ ค์›€
  • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ์ฝ”๋“œ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›€

2. Context API

React์˜ createContext๋ฅผ ํ™œ์šฉํ•ด ์ „์—ญ ์ƒํƒœ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ์‹.

const CounterContext = createContext();

function CounterProvider({ children }) {
  const [count, setCount] = useState(0);
  return (
    <CounterContext.Provider value={{ count, setCount }}>
      {children}
    </CounterContext.Provider>
  );
}

function GrandChild() {
  const { count, setCount } = useContext(CounterContext);
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

function App() {
  return (
    <CounterProvider>
      <GrandChild />
    </CounterProvider>
  );
}

๐Ÿš€ ์žฅ์ 

  • Prop Drilling ๋ฌธ์ œ ํ•ด๊ฒฐ
  • ๊ฐ„๋‹จํ•œ ์ƒํƒœ ๊ด€๋ฆฌ์— ์ ํ•ฉ

โš ๏ธ ๋‹จ์ 

  • ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ๋ชจ๋“  ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋จ
  • ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ค๋ฃจ๊ธฐ์— ๋ถ€์กฑํ•จ

6๏ธโƒฃ ๊ฒฐ๋ก 

  1. ๊ณผ๊ฑฐ์—๋Š” Prop Drilling๊ณผ Context API๋ฅผ ์ฃผ๋กœ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ, ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ต๊ณ  ์„ฑ๋Šฅ ์ด์Šˆ๊ฐ€ ์žˆ์—ˆ์Œ.
  2. Redux๋Š” ๊ฐ•๋ ฅํ•˜์ง€๋งŒ, Boilerplate ์ฝ”๋“œ๊ฐ€ ๋งŽ์•„ ์„ค์ •์ด ๋ณต์žกํ•จ.
  3. Zustand๋Š” Redux๋ณด๋‹ค ๊ฐ„๊ฒฐํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šฐ๋ฉฐ, ์„ฑ๋Šฅ๋„ ์ข‹์Œ.
  4. ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์—๋Š” Redux, ์ค‘์†Œ๊ทœ๋ชจ์—๋Š” Zustand๊ฐ€ ์ ํ•ฉํ•จ.

๐Ÿ‘‰ โ€œ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ, ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ํ•„์š”ํ•˜๋ฉด Reduxโ€
๐Ÿ‘‰ โ€œ๊ฐ„๋‹จํ•˜๊ณ  ๊ฐ€๋ณ๊ฒŒ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋ ค๋ฉด Zustandโ€ ๐Ÿš€

profile
๋‚˜, ๊ฐ€๋Šฅ

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