TL;DR
export const createUserSlice: StateCreator<UserSlice> = (set) =>
stateCreator<UserSlice>('search', {
name: '',
status: false,
gender: ''
})(set)
Zustand를 쓸 때 다음과 같은 코드를 쓸 때가 많았다.
const createUserSlice: StateCreator<UserSlice> = (set) => ({
user: {
name: '',
status: 'active',
gender: 'Male',
setName: (name: string) =>
set(
produce((draft) => {
draft.user.name = name
})
),
setStatus: (status: UserStatus) =>
set(
produce((draft) => {
draft.user.status = status
})
),
setGender: (gender: UserGender) =>
set(
produce((draft) => {
draft.search.gender = gender
})
)
}
})
액션 부분이 거의 중복되는 코드로 이루어져있었다. 그래서 set과 draft를 추상화했다.
const handleDraft =
<S extends keyof Store>(slice: S) =>
<T extends keyof Store[S]>(
key: T,
valueOrUpdater: ValueOrUpdater<Store[S][T]>
) =>
(state: Store) =>
produce(state, (draft) => {
draft[slice][key] =
typeof valueOrUpdater === 'function'
? (valueOrUpdater as Updater<Store[S][T]>)(draft[slice][key])
: valueOrUpdater
})
export const stateSetter =
<S extends keyof Store>(
slice: S,
set: Get<Mutate<StoreApi<Store>, []>, 'setState', undefined>
) =>
<T extends keyof Store[S]>(key: T) =>
(valueOrUpdater: ValueOrUpdater<Store[S][T]>) =>
set(handleDraft(slice)(key, valueOrUpdater))
export const createUserSlice: StateCreator<UserSlice> = (set) => {
const userSetter = stateSetter('user', set)
return {
user: {
name: '',
status: 'active',
gender: 'Male'
},
userActions: {
setName: searchSetter('name'),
setStatus: searchSetter('status'),
setGender: searchSetter('gender')
}
}
}
훨씬 깔끔해졌지만 그래도 중복되는게 보인다. 이번엔 actions 객체를 만드는 함수를 만들었다.
export const actionGenerator = <T extends keyof Store>(
key: T,
states: Store[T],
set: Get<Mutate<StoreApi<Store>, []>, 'setState', undefined>
): Setter<Store[T]> => {
const setter = stateSetter(key, set)
const actions: any = {}
Object.keys(states).forEach((state) => {
actions[`set${state[0].toUpperCase()}${state.slice(1)}`] = setter(
state as keyof Store[T]
)
})
return actions as Setter<Store[T]>
}
export const createUserSlice: StateCreator<UserSlice> = (set) => {
const init = {
name: '',
status: 'active',
gender: 'Male'
}
const userActions = actionGenerator('user', init, set)
return {
user: init,
userActions: userActions
}
}
이렇게 만들고 보니까 아예 slice를 만드는 함수를 만들고싶어졌다.
export const stateCreator =
<S extends Slice>(key: keyof Store, init: Store[keyof Store]) =>
(set: Get<Mutate<StoreApi<Store>, []>, 'setState', undefined>) => {
const actions = actionGenerator(key, init, set)
const slice = {
[key]: init,
[`${key}Actions`]: actions
}
return slice as unknown as S
}
export const createUserSlice: StateCreator<UserSlice> = (set) =>
stateCreator<UserSclie>('user', {
name: '',
status: 'active',
gender: 'Male'
})(set)
깔끔해졌다. 다만 중간에 구현한 유틸 함수들이 더 깔끔하게 만들어질수 있었을 것 같다. 내 typescript 경험치의 한계로 인해 as가 좀 들어간것도 좀 슬프다
© 5vermind.