- 기본적으로 Zustand는 셀렉터 구조로 state를 가져와 사용함
const bears = useStore((state) => state.bears)- 하지만 스토어가 많이 쪼개져 있는 실무 프로젝트 구조 상에서는, 스토어에서 state와 action을 가져오는 코드가 너무 길어지고 있었음
- 이를 개선할 수 있는 방법은?
import { StoreApi, UseBoundStore } from 'zustand'
type WithSelectors<S> = S extends { getState: () => infer T }
? S & { use: { [K in keyof T]: () => T[K] } }
: never
const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(
_store: S,
) => {
let store = _store as WithSelectors<typeof _store>
store.use = {}
for (let k of Object.keys(store.getState())) {
;(store.use as any)[k] = () => store((s) => s[k as keyof typeof s])
}
return store
}
export const useOrderStore = createSelectors(
create<IOrderState>()(
devtools(
(set, get) => ({
orderType: null,
isBid: false,
completed: false,
setOrderType: (orderType: OrderType) => set({ orderType }),
setIsBid: (isBid: boolean) => set({ isBid }),
setCompleted: (completed: boolean) => set({ completed }),
}),
{
name: 'useOrderStore',
},
),
),
)
// 기존 Zustand에서 사용하는 방식대로 스토어를 create()해서 구성하고,
// 해당 스토어를 createSelectors로 묶어서 export 해준다.
const isBid = useOrderStore.use.isBid()
const setIsBid = useOrderStore.use.setIsBid()
// 이런식으로 간단하게 state나 action을 가져다 사용할 수 있음.
// ❌ 스토어 사용이 가능은 하지만 모든 state가 변경될 때마다 컴포넌트를 업데이트 할 것임.
const store = useOrderProductStore() useShallow() 사용 중// ✅ 객체 형태로 여러 개의 state를 가져오는 방식
const {setName, setThumb, setBrandBgColor} = useOrderProductStore
useShallow((state) => {
setName: state.setName,
setThumb: state.setThumb,
setBrandBgColor: state.setBrandBgColor,
})
)
// ✅ 배열 형태로 여러 개의 state를 가져오는 방식
const [setName, setThumb, setBrandBgColor] = useOrderProductStore
useShallow((state) => [
state.setName,
state.setThumb,
state.setBrandBgColor,
]),
)// 스토어 세팅
const useOrderProductStore = createSelectors(
create<OrderProductState>()(
devtools(
(set, get) => ({
name: '',
thumb: '',
brandBgColor: '',
size: '',
inventoryId: 0,
productGrade: PRODUCT_GRADE_TRADE_CODE,
actions: {
setName: (name: string) => set((state) => ({ name })),
setThumb: (thumb: string) => set((state) => ({ thumb })),
setBrandBgColor: (brandBgColor: string) => set((state) => ({ brandBgColor })),
setSize: (size: string) => set((state) => ({ size })),
setInventoryId: (inventoryId: number) => set((state) => ({ inventoryId })),
setProductGrade: (productGrade) => set(() => ({ productGrade })),
},
}),
{
name: 'useOrderProductStore',
},
),
),
)
// ✅ 스토어에서 selector를 하나의 훅으로 export
export const useOrderProductStoreActions = () => useOrderProductStore((state) => state.actions)
****
// ✅ 컴포넌트에서 import 하여 사용(구조분해 할당 가능)
const { setName, setThumb, setBrandBgColor, setSize, setProductGrade } =
useOrderProductStoreActions()
dispatch 함수를 정의할 수 있음
const types = { increase: 'INCREASE', decrease: 'DECREASE' }
const reducer = (state, { type, by = 1 }) => {
switch (type) {
case types.increase:
return { grumpiness: state.grumpiness + by }
case types.decrease:
return { grumpiness: state.grumpiness - by }
}
}
const useGrumpyStore = create((set) => ({
grumpiness: 0,
dispatch: (args) => set((state) => reducer(state, args)),
}))
const dispatch = useGrumpyStore((state) => state.dispatch)
dispatch({ type: types.increase, by: 2 })
https://tkdodo.eu/blog/working-with-zustand
https://docs.pmnd.rs/zustand/guides/practice-with-no-store-actions
https://docs.pmnd.rs/zustand/guides/auto-generating-selectors
https://docs.pmnd.rs/zustand/guides/flux-inspired-practice#redux-like-patterns