๐Ÿคช ๋‚˜๋งŒ ๋ชฐ๋ž๋˜ Zustand

hoheesuยท2025๋…„ 4์›” 14์ผ

REACT

๋ชฉ๋ก ๋ณด๊ธฐ
4/5
post-thumbnail

ํ•ญ์ƒ ๋‚˜๋Š” ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ค‘์—๋Š” zustand๊ฐ€ ๊ฐ€์žฅ ํŽธํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์—ˆ๋‹ค. ์žฅ์ ๋ฐ–์— ์—†๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ๋ฐ ์™œ ๋ฆฌ๋•์Šค๋ž‘ ์ฐจ์ด๊ฐ€ ์žˆ๋Š”๊ฑธ๊นŒ? ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ ค๋˜ ์ฐฐ๋‚˜ ๋‚ด๊ฐ€ ์•Œ๊ณ  ์žˆ๋˜ zustand๋Š” ๊ทธ๋ƒฅ ์•„๋ฌด๊ฒƒ๋„ ์•„๋‹ˆ์˜€๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค.
๊ทธ๋™์•ˆ ์ •๋ง ๊ฐ€๋ฒผ์šด ์ „์—ญ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ ์ตœ๊ทผ ํ”„๋กœ์ ํŠธ์—์„œ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ deepํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ฒผ๋‹ค.

Provider

provider ๊ทธ๊นŒ์ด๊บผ ํ•„์š”๋Š” ํ•ด?

๐Ÿซข ํ•œ ํ™”๋ฉด์— ๋™์ผํ•œ ์ข…๋ฅ˜์˜ ์ปดํฌ๋„ŒํŠธ ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ๋™์‹œ์— ๋ Œ๋”๋ง๋˜๊ณ , ๊ฐ๊ฐ ๋…๋ฆฝ์ ์ธ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•ด์•ผ ํ•  ๋•Œ (์˜ˆ: ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋™์ผํ•œ ์œ„์ ฏ, ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋™์‹œ ํŽธ์ง‘ ์ฐฝ)๊ฐ€ Provider๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋…๋ฆฝ์ ์ธ ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์ƒํƒœ๋ฅผ ๊ฒฉ๋ฆฌํ•ด์•ผ ํ•œ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ ๊ธ€ํ†ค ์Šคํ† ์–ด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๊ณ , ๋น„ํšจ์œจ์ ์ธ ์ฝ”๋“œ๊ฐ€ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

์ด Provier๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์ด Zustand์˜ ์žฅ์ ์ด์ž ์ตœ๋Œ€ ๋‹จ์ ์ธ ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐ„๋‹จํ•œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” zustand๊ฐ€ ํ›จ์”ฌ ์œ ๋ฆฌํ•˜๊ณ , ๊ฐ„ํŽธํ•˜๋‹ค๊ณ  ๋А๋ผ๋Š” ๊ฒƒ์ด๊ณ , redux ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ๋น„๊ต์  ๋ณต์žกํ•˜๊ณ  ๊ฐ๊ฐ์˜ ๋…๋ฆฝ์ ์ธ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ๋” ์ ํ•ฉํ•˜๋‹ค๊ณ  ๋А๊ปด์ง€๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

์•„์ง jotai๋‚˜ recoil์€ ์‚ฌ์šฉํ•ด๋ณธ์ ์ด ์—†์–ด์„œ ์•„์‰ฝ๋‹ค ใ… 

๊ทธ๋ ‡๋‹ค๋ฉด ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ถ€๋ถ„ ~~ โ˜๏ธ
๊ณผ์—ฐ zustand์—์„œ๋Š” Provider๋ฅผ ์ „ํ˜€ ์‚ฌ์šฉํ•  ์ˆ˜๊ฐ€ ์—†์„๊นŒ?
์•„์ง zustand์—์„œ ์ง์ ‘์ ์œผ๋กœ Provider๋ฅผ ์ œ๊ณตํ•ด์ฃผ๊ณ  ์žˆ์ง€๋Š” ์•Š์ง€๋งŒ ๋ฆฌ์•กํŠธ์˜ context์™€ ํ•จ๊ป˜ ์“ด๋‹ค๋ฉด ๊ฐ€๋Šฅ ํ•˜๋‹ค!!!

import React, { createContext, useContext, useState } from 'react';
import { useStore } from 'zustand';
import { createStore } from 'zustand/vanilla';

// 1. ์Šคํ† ์–ด ์ƒ์„ฑ ๋กœ์ง ์ •์˜ (Provider ๋‚ด์—์„œ ์ง์ ‘ ์‚ฌ์šฉ)
//    (์˜ˆ์‹œ: ๊ฐ„๋‹จํ•œ ์นด์šดํ„ฐ ์Šคํ† ์–ด)
const createCounterStore = () => createStore((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

// 2. React Context ์ƒ์„ฑ
//    ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ReturnType<typeof createCounterStore> | null ๋กœ ํƒ€์ž… ์ง€์ •
const StoreContext = createContext(null);

// 3. Provider ์ปดํฌ๋„ŒํŠธ
export const CounterStoreProvider = ({ children }) => {
  // useState์˜ ์ดˆ๊ธฐ๊ฐ’ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜์Œ ๋งˆ์šดํŠธ๋  ๋•Œ
  // createCounterStore()๊ฐ€ ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœ๋˜์–ด ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ƒํƒœ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  const [store] = useState(() => createCounterStore());

  // ์ƒ์„ฑ๋œ ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค๋ฅผ Context Provider์˜ value๋กœ ์ „๋‹ฌ
  return (
    <StoreContext.Provider value={store}>
      {children}
    </StoreContext.Provider>
  );
};

// 4. Context๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปค์Šคํ…€ ํ›…
export const useCounterStore = (selector) => {
  // useContext๋กœ Provider๊ฐ€ ์ œ๊ณตํ•œ ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  const store = useContext(StoreContext);
  if (!store) {
    // Provider๋กœ ๊ฐ์‹ธ์ ธ ์žˆ์ง€ ์•Š์œผ๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ
    throw new Error('Cannot find CounterStoreProvider. Make sure to wrap the component tree.');
  }
  // Zustand์˜ useStore ํ›…์— Context๋กœ๋ถ€ํ„ฐ ์–ป์€ ์Šคํ† ์–ด์™€ selector๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  return useStore(store, selector);
};

// --- ์‚ฌ์šฉ ์˜ˆ์‹œ ---

// Counter ์ปดํฌ๋„ŒํŠธ (์ƒํƒœ ์‚ฌ์šฉ)
function CounterDisplay() {
  // ์ปค์Šคํ…€ ํ›…์„ ํ†ตํ•ด ์ƒํƒœ ๊ฐ’ ์ ‘๊ทผ
  const count = useCounterStore((state) => state.count);
  return <span>Count is: {count}</span>;
}

// Controls ์ปดํฌ๋„ŒํŠธ (์ƒํƒœ ๋ณ€๊ฒฝ ํ•จ์ˆ˜ ์‚ฌ์šฉ)
function CounterControls() {
  // ์ปค์Šคํ…€ ํ›…์„ ํ†ตํ•ด ์ƒํƒœ ๋ณ€๊ฒฝ ํ•จ์ˆ˜ ์ ‘๊ทผ
  const increment = useCounterStore((state) => state.increment);
  const decrement = useCounterStore((state) => state.decrement);
  return (
    <div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

// App ์ปดํฌ๋„ŒํŠธ (Provider๋กœ ๊ฐ์‹ธ๊ธฐ)
export default function App() {
  return (
    <div>
      <h2>Counter 1 (Independent)</h2>
      {/* Provider๋กœ ๊ฐ์‹ธ์ฃผ๋ฉด ์ด ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ์ฒซ ๋ฒˆ์งธ ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉ */}
      <CounterStoreProvider>
        <CounterDisplay />
        <CounterControls />
      </CounterStoreProvider>

      <hr />

      <h2>Counter 2 (Independent)</h2>
      {/* ์ƒˆ๋กœ์šด Provider๋Š” ์ƒˆ๋กœ์šด ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์‚ฌ์šฉ */}
      <CounterStoreProvider>
        <CounterDisplay />
        <CounterControls />
        {/* ์ด Provider ์•ˆ์— ๋˜ ๋‹ค๋ฅธ CounterDisplay๋ฅผ ๋„ฃ์–ด๋„ ๋™์ผํ•œ ์Šคํ† ์–ด ๊ณต์œ  */}
        <CounterDisplay />
      </CounterStoreProvider>
    </div>
  );
}

๋Œ€์ถฉ๋ด๋„ ์ƒ๋‹นํžˆ ๋ณต์žกํ•˜๊ธด ํ•˜์ง€๋งŒ, ๊ทธ๋ž˜๋„ ๋ง‰์ƒ ์‚ฌ์šฉ์„ ํ•ด๋ณด๋ฉด ์•„์ฃผ ๋ณต์žกํ•˜์ง€๋งŒ์€ ์•Š๋‹ค. ์ด๋Ÿฐ์‹์œผ๋กœ zustand๋ฅผ ์ƒํ™ฉ์— ๋งž์ถฐ ์‚ฌ์šฉ์„ ํ•œ๋‹ค๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด์„œ๋„, ๋…๋ฆฝ์ ์ธ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ๊ตฌํ˜„ ํ•  ์ˆ˜ ์žˆ๋Š” ์•„์ฃผ ์ข‹์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

๐Ÿฅ˜ useShallow

Zustand์—์„œ useShallow(๋˜๋Š” shallow ํ•จ์ˆ˜)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ€์žฅ ํฐ ์ด์œ ๋Š” ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์„ ๋•Œ, ์–•์€(shallow) ๋น„๊ต๋กœ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ง‰์•„์„œ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.
๋ณดํ†ต Zustand๋กœ ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ,

const { increase, decrease } = useCountStore(
  useShallow((state) => ({
    increase: state.increase,
    decrease: state.decrease,
  }))
)ใ…

์ด๋Ÿฐ ์‹์œผ๋กœ ๋งˆ์ง€๋ง‰์— shallow ๋˜๋Š” useShallow ๊ฐ™์€ ๊ฑธ ๋ถ™์ด๋ฉด, ์ด์ „์— ๋‚ด๊ฐ€ ๊ตฌ๋…ํ•œ ๊ฐ’๋“ค์ด๋ž‘ ์ƒˆ๋กœ ๊ตฌ๋…ํ•œ ๊ฐ’๋“ค์ด ์–•์€ ๋น„๊ต(===)๋กœ ๋ฐ”๋€Œ์—ˆ๋Š”์ง€ ์•ˆ ๋ฐ”๋€Œ์—ˆ๋Š”์ง€๋ฅผ ์ฒดํฌ๋ฐ”๋€Œ์ง€ ์•Š์•˜๋‹ค๋ฉด ํ•ด๋‹น React ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Œ.

์ฆ‰, ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ƒํƒœ๋ฅผ ํ•œ๊บผ๋ฒˆ์— ๊ฐ€์ ธ์™€๋„ ์‹ค์ œ๋กœ๋Š” ์ „๋ถ€ ๊ฐ์ฒด/๋ฐฐ์—ด ๋ ˆํผ๋Ÿฐ์Šค๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š์•˜๋‹ค๋ฉด ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ฐ๊ฒŒ๋˜์–ด, ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ๋” ๋ถ€๋“œ๋Ÿฌ์šด ์„ฑ๋Šฅ์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

์ •๋ฆฌํ•˜์ž๋ฉด, useShallow๋Š” Zustand ์Šคํ† ์–ด์—์„œ ์ƒํƒœ ์—ฌ๋Ÿฌ ๊ฐœ๋ฅผ ๋™์‹œ์— ๊ตฌ๋…ํ•  ๋•Œ, ์ด ๊ฐ’๋“ค์ด ์–•์€ ๋น„๊ต๋กœ ๋ณ€ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ๋ฆฌ๋ Œ๋”๋ฅผ ์•ˆ ํ•˜๊ฒ ๋‹ค๋Š” ๋กœ์ง์„ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋น„๊ต ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

profile
๐Ÿค”๐Ÿ‘๐Ÿ’ก๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป๐Ÿคฏ๐Ÿ˜‡

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