next_js
web

2025-04-14

Zustand란?

Zustand는 상태관리 라이브러리다. 상태는 React의 state할때 그 상태이다. 그리고 Zustand는 독일어로 상태라는 뜻임. (나도 라이브러리를 만들면 이름을 Sangte라고 지을 수 있을까?)

리액트도 useState, useContext가 있는데 왜 라이브러리를 쓸까?

  • prop drilling - 모든 상태를 prop으로 주고받기는 힘들다.
  • 한 context를 같이 쓰는 모든 컴포넌트가 다같이 렌더되어 쓸데없는 렌더가 일어난다.

Zustand의 장점

  • state 정의랑 그걸 변경하는 함수를 한곳에 쓸 수 있다.
  • 그리고 글로벌하게 어디서나 갖다쓸 수 있다. 밑에 코드 써놨지만 만든 함수를 import해서 쓸 수 있다. (다른 상태관리라이브러리는 안써봐서 모르겠지만 꽤나 간편한 것 같다)
  • 정확히 변한 값을 쓰는 컴포넌트만 re-render한다.
  • Redux는 context provider로 컴포넌트를 wrap해야 한다는데 그런 거 없다.
use when…Recommended
State is local to a componentuseState
Sharing state between a few componentsuseContext
Global state, lots of components need itZustand / Redux
Need performance + simplicityZustand

쓰는법

  1. npm install zustand
  2. create a store
    import { create } from 'zustand'
    
    const useStore = create((set) => ({
      bears: 0,
      increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
      removeAllBears: () => set({ bears: 0 }),
      updateBears: (newBears) => set({ bears: newBears }),
    }))
    
  3. bind the component
    function BearCounter() {
      const bears = useStore((state) => state.bears)
      return <h1>{bears} bears around here...</h1>
    }
    
    function Controls() {
      const increasePopulation = useStore((state) => state.increasePopulation)
      return <button onClick={increasePopulation}>one up</button>
    }
    

예전에 내가 어떻게썼는지 복습

실시간으로 호버한 물체에 따라 뭔가 스타일을 바꿔야 할 때 사용했다. 날짜를 가지고 있는 calendar-entry에 호버하면 → calendar-head에 해당 날짜를 보여준다.

  • lib/useHoveredStore.ts

    import { create } from 'zustand';
     
    interface HoveredEntryState {
      hoveredDate: {startDate: string, endDate: string} | null;
      setHoveredDate: (date: {startDate: string, endDate: string} | null) => void;
    }
     
    export const useHoveredStore = create<HoveredEntryState>((set) => ({
      hoveredDate: null,
      setHoveredDate: (date) => set({ hoveredDate: date }),
    }));
    

    state랑 setState를 같이 만들고

  • ui/…/calendar-entry.tsx

    import { useHoveredStore } from "@/app/lib/useHoveredStore";
    ...
    const setHoveredDate = useHoveredStore((state) => state.setHoveredDate);
    ...
    
    < ...
    	onMouseEnter={() => setHoveredDate({ startDate: data.startDate, endDate: data.endDate })}
      onMouseLeave={() => setHoveredDate(null)}
    >
    

    호버하면 hoveredDate를 set하고, 딴데가면 초기화

  • ui/…/calendar-head.tsx

    import { useHoveredStore } from "@/app/lib/useHoveredStore";
    ...
    const hoveredDate = useHoveredStore((state) => state.hoveredDate);
    ...
    
    {hoveredDate ? (
       <div
         className="relative bg-gray-200 mix-blend-multiply w-auto rounded-full h-8"
         style={{
           gridRow: `1 / span ${entry_count}`,
           gridColumnStart: (+new Date(hoveredDate.startDate) - +new Date(calendarEntries[0].startDate)) / (1000 * 60 * 60 * 24)+1,
           gridColumnEnd: (+new Date(hoveredDate.endDate) - +new Date(calendarEntries[0].startDate)) / (1000 * 60 * 60 * 24)+2
         }}
       >
       </div>
     ) : (
       null
     )}
    

    hoveredDate가 있으면(현재 호버 중이면) 색깔 막대기를 그 날짜에 맞는 위치에다 만들어주고, 없으면 null