[TIL-0511] Zustand create() vs. create()()

jinyยท2025๋…„ 5์›” 27์ผ

์บก์Šคํ†ค2

๋ชฉ๋ก ๋ณด๊ธฐ
12/22

๐ŸŒŸ zustand store์—์„œ create(...)์™€ create(...)()

zustand store๋ฅผ ์ •์˜ํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ๋ณด๋ฉด, create(...)์ฒ˜๋Ÿผ ๊ด„ํ˜ธ ํ•˜๋‚˜๋งŒ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋„ ์žˆ๊ณ , create(...)()์™€ ๊ฐ™์ด ๊ด„ํ˜ธ ๋‘ ๊ฐœ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋„ ์žˆ๋‹ค.

๊ตฌ๋ถ„create(...)create(...)()
์˜๋ฏธstore ์ƒ์„ฑ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•จstore ๊ฐ์ฒด ๊ทธ ์ž์ฒด๋ฅผ ๋ฐ”๋กœ ์ƒ์„ฑํ•จ
์‚ฌ์šฉ ์ƒํ™ฉ๋ณดํ†ต ๋‹ค๋ฅธ ํŒŒ์ผ์—์„œ store๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋กœ ์‚ฌ์šฉํ•  ๋•Œ๋ณดํ†ต ์ฆ‰์‹œ ์‚ฌ์šฉํ•  store๋ฅผ ๋งŒ๋“ค ๋•Œ
๋ฐ˜ํ™˜๊ฐ’store ์ƒ์„ฑ ํ•จ์ˆ˜store ๊ฐ์ฒด (Hook ํฌํ•จ)
์šฉ๋„๋‚˜์ค‘์— ์—ฌ๋Ÿฌ ๊ฐœ ์ƒ์„ฑํ•˜๊ณ  ์‹ถ์„ ๋•Œ์ „์—ญ store ํ•˜๋‚˜ ๋งŒ๋“ค ๋•Œ
์‚ฌ์šฉ ์˜ˆํŒฉํ† ๋ฆฌ ํŒจํ„ด, ํ…Œ์ŠคํŠธ์šฉ store์‹ค์ œ ์•ฑ ์ƒํƒœ ๊ด€๋ฆฌ

create(...)์™€ create(...)()๋Š” ๋ณด๊ธฐ์—” ๊ด„ํ˜ธ ํ•˜๋‚˜ ์ฐจ์ด์ง€๋งŒ, ํƒ€์ž… ์ฒ˜๋ฆฌ ๋ฐฉ์‹, ์‚ฌ์šฉ ๋ชฉ์ , ํ™•์žฅ์„ฑ ๋ฉด์—์„œ ์ค‘์š”ํ•œ ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค.
์ด์ œ๋ถ€ํ„ฐ create(...)์™€ create(...)() ์ฐจ์ด์ ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์ž.


๐ŸŒŸ create(...) : "ํ•จ์ˆ˜"๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ˜•ํƒœ

const makeUserStore = () => {
  return create((set) => ({
    data: null,
    setData: (data: any) => set({ data }),
  }));
};
  • ์ด ๊ฒฝ์šฐ create(...)๋Š” store๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•จ

    • makeUserStore()๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ์ง„์งœ store๊ฐ€ ๋งŒ๋“ค์–ด์ง

    • ์ฆ‰, ์ด ๊ตฌ์กฐ๋Š” store๋ฅผ ๋‚˜์ค‘์— ์›ํ•˜๋Š” ์‹œ์ ์— ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ ์œ ์šฉํ•จ

      const userStore = makeUserStore(); // ์‹ค์ œ store ๊ฐ์ฒด ์ƒ์„ฑ
      
      // ์ƒํƒœ ์ฝ๊ธฐ
      const currentData = userStore.getState().data;
      console.log("ํ˜„์žฌ ๋ฐ์ดํ„ฐ:", currentData);
      
      // ์ƒํƒœ ๋ณ€๊ฒฝ
      userStore.getState().setData("Hello Zustand!");
      
  • ์™œ ์ด๋ ‡๊ฒŒ ์“ฐ๋‚˜?

    • ์—ฌ๋Ÿฌ store ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ (์˜ˆ: ์‚ฌ์šฉ์ž๋ณ„ store)

      // factory.ts
      import { create } from "zustand";
      
      export const makeUserStore = (initialName: string) => create(() => ({
        name: initialName,
        setName: (newName: string) => set({ name: newName }),
      }));
      // example.ts
      const userStore1 = makeUserStore("Alice");
      const userStore2 = makeUserStore("Bob");
      
      console.log(userStore1.getState().name); // Alice
      console.log(userStore2.getState().name); // Bob
      • ์ด๋ ‡๊ฒŒ ์“ฐ๋Š” ์ด์œ 
        • ํ•˜๋‚˜์˜ useUserStore Hook์„ ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ
        • userStore1, userStore2๋Š” ์„œ๋กœ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜์ง€ ์•Š์Œ โ†’ ๋ฉ€ํ‹ฐ ์ธ์Šคํ„ด์Šค ๊ด€๋ฆฌ ๊ฐ€๋Šฅ
    • ์œ ๋‹› ํ…Œ์ŠคํŠธ์—์„œ isolated store๋ฅผ ๋งŒ๋“ค ๋•Œ

      // counterStore.ts
      import { create } from "zustand";
      
      export const makeCounterStore = () => create((set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 })),
      }));
      // counterStore.test.ts
      import { makeCounterStore } from "./counterStore";
      
      test("increment increases count", () => {
        const store = makeCounterStore();
        expect(store.getState().count).toBe(0);
        
        store.getState().increment();
        expect(store.getState().count).toBe(1);
      });
      • ์ด๋ ‡๊ฒŒ ์“ฐ๋Š” ์ด์œ 
        • ํ…Œ์ŠคํŠธํ•  ๋•Œ ์ƒํƒœ๊ฐ€ ๋…๋ฆฝ์ ์ด์–ด์•ผ ํ•จ (๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์™€ ๊ฐ„์„ญ ์—†์Œ)
        • create()๋ฅผ ํ•จ์ˆ˜๋กœ ๊ฐ์‹ธ์„œ store๋ฅผ ๋งค๋ฒˆ ์ƒˆ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ โ†’ ํ…Œ์ŠคํŠธ์˜ ์ˆœ์ˆ˜์„ฑ ์œ ์ง€
    • DI(Dependency Injection) ํŒจํ„ด์ฒ˜๋Ÿผ store๋ฅผ ์ฃผ์ž…ํ•  ํ•„์š”๊ฐ€ ์žˆ์„ ๋•Œ

      // storeFactory.ts
      import { create } from "zustand";
      
      type TodoStore = {
        todos: string[];
        addTodo: (todo: string) => void;
      };
      
      // ๋…๋ฆฝ๋œ store ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๋Š” ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜
      export const makeTodoStore = (initialTodos: string[] = []) => create<TodoStore>((set) => ({
        todos: initialTodos,
        addTodo: (todo) => set((state) => ({ todos: [...state.todos, todo] })),
      }));

      โœ๏ธ storeFactory.ts ์„ค๋ช…

      • makeTodoStore() : zustand์˜ create(...)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜
      • ํ˜ธ์ถœํ•  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด store๊ฐ€ ์ƒ์„ฑ๋˜๋ฏ€๋กœ, ์„œ๋กœ ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ
      • initialTodos๋Š” ์ดˆ๊ธฐ๊ฐ’์„ ์™ธ๋ถ€์—์„œ ์ „๋‹ฌ๋ฐ›์Œ โ†’ ์ดˆ๊ธฐ ์ƒํƒœ ์œ ์—ฐํ•˜๊ฒŒ ์„ค์ • ๊ฐ€๋Šฅ
      // TodoComponent.tsx
      import { FC } from "react";
      import { makeTodoStore } from "./storeFactory";
      
      interface Props {
        store: ReturnType<typeof makeTodoStore>; // store ํƒ€์ž… ์ง€์ •
      }
                          
      const TodoCompoent: FC<Props> = ({ store }) => {
        const todos = store((state) => state.todos); // ์ƒํƒœ ๊ตฌ๋…
        const addTodo = store((state) => state.addTodo); // ์•ก์…˜ ๊ตฌ๋…
        
        return (
          <div>
          	<button onClick={() => addTodo("New Task")}>Add</button>
              <ul>{todos.map((todo, idx) => <li key={idx}>{todo}</li>)}</ul>
          </div>
        );
      };

      โœ๏ธ TodoComponent.tsx ์„ค๋ช…

      • store๋Š” props๋กœ ์ „๋‹ฌ๋ฐ›์€ zustand store ์ธ์Šคํ„ด์Šค
      • store((state) => state.todos) : ์ƒํƒœ๋ฅผ ๊ตฌ๋…ํ•จ. ์ƒํƒœ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์ž๋™์œผ๋กœ ๋ฆฌ๋ Œ๋”๋ง๋จ
      • store((state) => sttad.addTodo) : ์•ก์…˜๋„ ์ด๋ ‡๊ฒŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
      • addTodo("New Task")๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ๋จ โ†’ todos ๋ฆฌ์ŠคํŠธ๋„ ์ž๋™ ์—…๋ฐ์ดํŠธ๋จ
      • ์ด ๊ตฌ์กฐ๋Š” React Hook์ธ useUserStore((state) => state.xxx)์™€ ์ •ํ™•ํžˆ ๊ฐ™์€ ๋™์ž‘์„ ํ•˜์ง€๋งŒ, ์ „์—ญ store๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์™ธ๋ถ€์—์„œ ์ฃผ์ž…ํ–ˆ๋‹ค๋Š” ์ ์ด ๋‹ค๋ฆ„
      // App.tsx
      const todoStore = makeTodoStore(["Read book"]);
      
      <TodoComponent store={todoStore} />;

      โœ๏ธ App.tsx ์„ค๋ช…

      • todoStore๋Š” ํ•˜๋‚˜์˜ zustand store ์ธ์Šคํ„ด์Šค
      • ["Read book"]์ด๋ผ๋Š” ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ์ƒ์„ฑ
      • ์ด store๋ฅผ TodoComponent์— props๋กœ ๋„˜๊ฒจ์คŒ
      • ์ด์ œ TodoComponent๋Š” ์™ธ๋ถ€์—์„œ ์ฃผ์ž…๋ฐ›์€ ์ด store๋งŒ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋จ
      • ์ด๋ ‡๊ฒŒ ์“ฐ๋Š” ์ด์œ 
        • store๋ฅผ ์™ธ๋ถ€์—์„œ ์ฃผ์ž…(DI: Dependency Injection) โ†’ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ด๋–ค store๋ฅผ ์“ธ์ง€ ์œ ์—ฐํ•˜๊ฒŒ ๊ฒฐ์ • ๊ฐ€๋Šฅ
        • ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ / ์‹ค์ œ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ store๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์Œ โ†’ ์ปดํฌ๋„ŒํŠธ ์žฌ์‚ฌ์šฉ์„ฑ ํ–ฅ์ƒ

๐ŸŒŸ create(...)() : ๋ฐ”๋กœ ์‹คํ–‰ํ•ด์„œ "store ๊ฐ์ฒด"๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ˜•ํƒœ

// ์ „์—ญ store (store๊ฐ€ ์ฆ‰์‹œ ๋งŒ๋“ค์–ด์ง)
const useUserStore = create((set) => ({
  data: null,
  setData: (data: any) => set({ data }),
}));
  • ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ๋ฒ•
    • create()์— ๋ฐ”๋กœ ํ•จ์ˆ˜ ๋„ฃ๊ณ  ๊ทธ ์ž๋ฆฌ์—์„œ ์‹คํ–‰ํ•ด์„œ ๋ฐ”๋กœ store ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ฆ
    • ๊ฒฐ๊ณผ์ ์œผ๋กœ useUserStore๋Š” hook์ฒ˜๋Ÿผ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” store ๊ฐ์ฒด์ž„
      const data = useUserStore((state) => state.data);
      • useUserStore๋Š” useStore Hook์ฒ˜๋Ÿผ ๋™์ž‘ํ•จ
      • ๋ณดํ†ต์€ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌํ•  ๋•Œ ์ด ๋ฐฉ์‹์œผ๋กœ ์”€

๐ŸŒŸ ๋‚ด๋ถ€ ๋™์ž‘ ์ฐจ์ด

  • create(...)์˜ ๋ฆฌํ„ด๊ฐ’์€ userStore Hook + store ๋ฉ”์„œ๋“œ๋ฅผ ๋‹ด์€ ๊ฐ์ฒด
  • ํ•˜์ง€๋งŒ create ์ž์ฒด๋Š” ํ•จ์ˆ˜์ด๋ฉฐ, ์‹คํ–‰๋˜์–ด์•ผ ์ง„์งœ store๊ฐ€ ์ƒ๊น€
  • ๋”ฐ๋ผ์„œ create(...)()๋Š” create(...)๋กœ ์„ค์ •๋งŒ ๋งŒ๋“  ํ›„ ()๋กœ ์ฆ‰์‹œ ์‹คํ–‰ํ•˜๋Š” ํ˜•ํƒœ

๐ŸŒŸ ์˜ˆ์ œ ๋น„๊ต

  1. create()๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ (ํ•จ์ˆ˜ ๋ฐ˜ํ™˜)

    const makeStore = () => create((set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
    }));
    
    const store1 = makeStore(); // ๊ฐ๊ฐ ๋‹ค๋ฅธ store
    const store2 = makeStore();
    • store1, store2๋Š” ๊ฐ๊ฐ ๋…๋ฆฝ๋œ ์ƒํƒœ ๊ฐ€์ง
  2. create()()๋กœ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ (๊ณต์šฉ store)

    const userCounterStore = create((set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
    }));
    • useCounterStore๋Š” ์•ฑ ์ „์ฒด์—์„œ ๋™์ผํ•œ store ์ธ์Šคํ„ด์Šค๋ฅผ ๊ณต์œ ํ•จ

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