today,weekly I learn

zustand 타입스크립트에서 사용하기(기본사용법)

rhdaud2 2024. 11. 19. 23:23

 

작고 빠르며  확장 가능한 상태 관리 솔루션.

 

 

 

docs 최상단에 제공된 zustand 사용법(에 타입 지정을 직접 추가함)

 

typeScript 가이드 : https://github.com/pmndrs/zustand/blob/main/docs/guides/typescript.md

 

zustand/docs/guides/typescript.md at main · pmndrs/zustand

🐻 Bear necessities for state management in React. Contribute to pmndrs/zustand development by creating an account on GitHub.

github.com

 

 

 

(store 생성)

기본형, 객체, 함수 무엇이든 들어올 수 있으며

set 함수는 상태를 병합한다( The set function merges state.)

import { create } from 'zustand'

type State = {
	bear: number;
}
type Actions = {
	increasePopulation: (state: number) => void;
    removeAllBears : (state: number) => void;
    updateBears: (newBears: number) => void;
}

const useStore = create<State & Actions>((set) => ({
  bear: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
  updateBears: (newBears) => set({ bears: newBears }),
}))

 

이후 component들을 바인딩

function BearCounter() {
  const bears = useStore((state) => state.bears)
  return <h1>{bears} around here...</h1>
}

function Controls() {
  const increasePopulation = useStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}

 

 


const state = useStore()

 

이와 같이 모든 구성 요소를 가져오는 것도 가능하지만, 

state가 변경될때마다 모든 구성 요소가 업데이트되기 때문에 권장되지 않는다.

 

const nuts = useBearStore((state) => state.nuts)
const honey = useBearStore((state) => state.honey)

이와 같이 store 전체가 아닌 필요한 state만 구독하는 것을 공식 문서에서 추천한다

 

 

또한 내부에 여러개의 상태 선택이 있는 객체를 구성하고싶을 때에,

useShallow 메서드를 사용해 해당 상태값을 비교해 값이 달라졌을때만 렌더링을 시킬 수 있다

 

import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'

const useBearStore = create((set) => ({
  nuts: 0,
  honey: 0,
  treats: {},
  // ...
}))

// Object pick, re-renders the component when either state.nuts or state.honey change
const { nuts, honey } = useBearStore(
  useShallow((state) => ({ nuts: state.nuts, honey: state.honey })),
)

// Array pick, re-renders the component when either state.nuts or state.honey change
const [nuts, honey] = useBearStore(
  useShallow((state) => [state.nuts, state.honey]),
)

// Mapped picks, re-renders the component when state.treats changes in order, count or keys
const treats = useBearStore(useShallow((state) => Object.keys(state.treats)))

 


 

[불변성 유지]

 

불변성이 깨질 경우, react가 상태 변경 여부를 정확히 알 수 없게 되어서(기존 값 자체가 변경되기때문에)

불필요한 렌더링 발생 혹은 상태 변경이 반영되지 않는 문제가 발생할 수 있다

 

(깊이 중첩된 객체를 사용하는 경우의 불변성 유지 예시)

 

//이와 같은 깊은 객체를 가질 경우
type State = {
  deep: {
    nested: {
      obj: { count: number }
    }
  }
}

 

1. ... 연산자를 사용해 기존 데이터를 건드리지 않고, 새로운 데이터 생성하기

 

normalInc: () =>
	set((state) => ({
    	deep: {
       		...state.deep,	//deep 객체 복사
            nested: {
            	...state.deep.nested,	//nested 객체 복사
                obj: {
                	...state.deep.nested.obj,	//obj 객체 복사
                    count: state.deep.nested.obj.count +1	//count 값만 변경
                }
            }
         }
     })),

 

=> 원본 state는 변하지 않고, 새로운 상태 객체 반환

 

 

2. immer 라이브러리 사용

  immerInc: () =>
    set(produce((state: State) => { ++state.deep.nested.obj.count })),

라이브러리를 사용해 더욱 간단하게 불변성을 유지할 수 있다

 

[immer() 메서드는

zustand/middleware/immer

에서 제공하지만 produce 메서드를 사용하려면 immer 라이브러리를 설치해야하는것으로 보인다]


 

비동기 action 

- 데이터 페칭 후 전역에 저장하고싶을 때 사용하면 될 듯 하다

const useFishStore = create((set) => ({
  fishies: {},
  fetch: async (pond) => {
    const response = await fetch(pond)
    set({ fishies: await response.json() })
  },
}))

 

 


subscribe()

 

- 리렌더링 발생 없이 상태에 바인딩하기(with Ref)

- useEffect와 결합해, 언마운트시 구독이 해제되도록 할 것

 

const useScratchStore = create((set) => ({ scratches: 0, ... }))

const Component = () => {
  // Fetch initial state
  const scratchRef = useRef(useScratchStore.getState().scratches)
  // Connect to the store on mount, disconnect on unmount, catch state-changes in a reference
  useEffect(() => useScratchStore.subscribe(
    state => (scratchRef.current = state.scratches)
  ), [])
  ...

 

 


storage를 사용한 미들웨어 지속

import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

const useFishStore = create(
  persist(
    (set, get) => ({
      fishes: 0,
      addAFish: () => set({ fishes: get().fishes + 1 }),
    }),
    {
      name: 'food-storage', // name of the item in the storage (must be unique)
      storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
    },
  ),
)

 

미들웨어 관련 문서

 

zustand/docs/integrations/persisting-store-data.md at main · pmndrs/zustand

🐻 Bear necessities for state management in React. Contribute to pmndrs/zustand development by creating an account on GitHub.

github.com