zustand를 Context로 사용해서 범위 제한하기

2023-08-01 | 4 min read

오늘 한 일

  • gloddy 개발

감기에 걸렸다..🥲

사실 2주 전부터 지금까지 계속 코감기가 있었는데 오늘은 정말 힘들었다.

카페에서 계속 기침하고 콧물이 나와서 집중이 안됐다. 그래서 공부를 많이 하진 못하고 일찍 잘 예정이다.


zustand를 Context로 사용해서 범위 제한하기

전 버전에서는 zustand/contextcreateContext를 제공했지만, 4 버전으로 넘어오면서 이렇게 사용하도록 권장하고 있다. (5에서는 제거될 예정)

// store.tsx
+ import { createContext, useContext } from "react";
- import create from "zustand";
- import createContext from "zustand/context";
+ import { createStore, useStore } from "zustand";

- const useStore = create((set) => ({
+ const store =  createStore((set) => ({
    bears: 0,
    increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
    removeAllBears: () => set({ bears: 0 })
  }));

+ const MyContext = createContext()

+ export const Provider = ({ children }) => <MyContext.Provider value={store}>{children}</MyContext.Provider>;

+ export const useMyStore = (selector) => useStore(useContext(MyContext), selector);

이렇게 사용하면 Provider로 감싸진 컴포넌트에서만 useMyStore를 사용할 수 있다.

createStore? create?

zustand의 내부 코드를 보면 이렇게 되어있다.

const createImpl = <T>(createState: StateCreator<T, [], []>) => {
  if (import.meta.env?.MODE !== 'production' && typeof createState !== 'function') {
    console.warn(
      "[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`."
    )
  }
  const api = typeof createState === 'function' ? createStore(createState) : createState

  const useBoundStore: any = (selector?: any, equalityFn?: any) =>
    useStore(api, selector, equalityFn)

  Object.assign(useBoundStore, api)

  return useBoundStore
}

export const create = (<T>(createState: StateCreator<T, [], []> | undefined) =>
  createState ? createImpl(createState) : createImpl) as Create

반환 타입을 보면 이렇다.

  • createStore<T>: StoreApi<T>
  • create<T>: UseBoundStore<StoreApi<T>>

결국 createcreateStore를 사용해서 만든 api를 useStore에 넘겨주는 역할을 한다.

타입스크립트에서 사용하기 (createStore ver. / create ver.)

타입스크립트를 사용한다면 이런식으로 작성할 수 있을 것 같다.

// createStore ver.
import { createContext, useContext } from 'react'
import { createStore, useStore } from 'zustand'

type State = {
  bears: number
  increasePopulation: () => void
  removeAllBears: () => void
}

const store = createStore<State>((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

const MyContext = createContext<typeof store | null>(null)

export const Provider = ({ children }: { children: React.ReactNode }) => (
  <MyContext.Provider value={store}>{children}</MyContext.Provider>
)

export const useMyStore = (selector: (state: State) => unknown) => {
  const store = useContext(MyContext)
  if (!store) throw new Error('useMyStore must be used within a Provider')

  return useStore(store, selector)
}

// create ver.
import { createContext, useContext } from 'react'
import { create, useStore } from 'zustand'

type State = {
  bears: number
  increasePopulation: () => void
  removeAllBears: () => void
}

const store = create<State>((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

const MyContext = createContext<typeof store | null>(null)

export const Provider = ({ children }: { children: React.ReactNode }) => (
  <MyContext.Provider value={store}>{children}</MyContext.Provider>
)

export const useMyStore = (selector: (state: State) => unknown) => {
  const store = useContext(MyContext)
  if (!store) throw new Error('useMyStore must be used within a Provider')

  return store(selector)
}

결론

저번에 고민했던 내용을 해결할 수 있을 것 같다.


내일 할 일

  • gloddy 회의 및 개발